Introduction
The article will assist you to decouple your dispatcher uses to a utility class that will execute the provided actions on the current dispatcher. This class will be very helpful, especially if you are dealing with the multi-threaded application or using TPL. The class is inspired from MVVM Light toolkit. I have made a few tweaks to make it more flexible and purposeful while using the available methods in this class.
Background
Many times while developing a decoupled application using techniques like MVVM and Threading, we come across scenarios where we want to execute a method, an action on Dispatcher from a view model or a helper/utility class. The example of such an action would be updating UI bound property which may throw cross-threading exception because the thread that was going to update the property is not an UI thread. To solve this, we use Dispatcher
methods like Dispatcher.Invoke
in those classes which lead to adding a reference to System.Windows.Application
to all these classes. The following class will do the work for you, executing UI bound actions from such a class (e.g., View Model or Utility) without referring to Application.Current
or Dispatcher.CurrentDispatcher
in that class.
Using the Code
Add this class to one of your common projects which are being referred in all other projects. Then, call the Initialize
method from the main window constructor. You can also call this method from any other window, user control constructor.
public static class UiDispatcherHelper
{
public static Dispatcher UiDispatcher { get; private set; }
public static void Initialize()
{
if (UiDispatcher != null && UiDispatcher.Thread.IsAlive)
return;
UiDispatcher = Dispatcher.CurrentDispatcher;
}
public static void BeginInvokeOnUi(Action action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
UiDispatcher.InvokeAsync(action, DispatcherPriority.Input);
}
public static void InvokeOnUi(Action action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
UiDispatcher.Invoke(action);
}
public static DispatcherOperation InvokeOnUiAsync(Action action, DispatcherPriority priority)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
return UiDispatcher.InvokeAsync(action, priority);
}
}
The BeginInvokeOnUi
method internally uses the InvokeAsync
method which is introduced in .NET 4.5. If your application is using the older .NET Framework, then replace the InvokeAsync
to BiginInvoke
method and you are done. The method abstracts the DispatcherPriority
from the caller and default uses the Input priority. This is a good if you want to delay the UI bound operation till the application is ready for the input.
public static void BeginInvokeOnUi(Action action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
UiDispatcher.InvokeAsync(action, DispatcherPriority.Input);
}
For the operations that want to specify the DispatcherPriority
, the following InvokeOnUiAsync
method does the same. It gives the caller more control on when the queued operation should be executed. This method can also be changed to the older implementation of BiginInvoke
if you are using the older .NET Framework.
public static DispatcherOperation InvokeOnUiAsync(Action action, DispatcherPriority priority)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
return UiDispatcher.InvokeAsync(action, priority);
}
And, you can utilize this class and its methods as follows:
private static void ShowMessageBox(string msgHeader, string message, EMessageBoxType eMessageBoxType)
{
UiDispatcherHelper.InvokeOnUi(() =>
{
});
}
This is how you can simply queue your operation from anywhere on to the dispatcher without even caring about which thread the method is being called on.
You can also do this:
var response = Task.Run(request);
response.ContinueWith(x => UiDispatcherHelper.BeginInvokeOnUi(() =>
{
});
Unit Test Support
When we use the dispatcher in our backend code, e.g. in ViewModel
class, it always results in difficulties for unit testing. This class is also no exception to that phenomenon. :)
Below is a workaround that I have used to support the unit testability of the code which is calling the method for this class.
[TestInitialize]
public void IntializeTest()
{
if (Application.Current == null)
{
Activator.CreateInstance(typeof(Application));
}
UiDispatcherHelper.Initialize();
}
This will create an AppDomain
instance while running the Unit test. I know it's not cool, but as I said, it's a workaround. :)
We also need to wait for the background / async operations to complete. For this purpose, I have used the following method:
protected void WaitForThreads()
{
int maxThreads = 0;
int placeHolder = 0;
int availThreads = 0;
int timeOutSeconds = 10;
while (timeOutSeconds > 0)
{
System.Threading.ThreadPool.GetMaxThreads(out
maxThreads, out placeHolder);
System.Threading.ThreadPool.GetAvailableThreads(out availThreads,
out placeHolder);
if (availThreads == maxThreads) break;
System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(1000));
--timeOutSeconds;
}
}
But this is not quite helpful in our scenario. Hence additionally, I have hooked to "ShutdownFinished
" event to get the notification for Dispatcher operation completion. This is the only event that I could use as there is no direct event that notifies the completion of the queued operation. This is how the code would look like in our "TestMethod
".
[TestMethod]
public void Save_Click()
{
WaitForThreads();
UiDispatcherHelper.UiDispatcher.ShutdownFinished += (sender, args) =>
{
Assert.IsTrue();
};
}
I hope this workaround works for many, obviously not for all the scenarios. :)
Point"s of Interest
Well, using this class has simplified many of our UI thread bound operations in our view model class. Also, to be frank, this class is not easy to unit test, but we still have a workaround for it.
This is my first blog, so if I miss something or there are some mistakes, please do let me know.
History
This is the first version of my article. I would update this to address the suggestions of the readers as well as for incremental updates if any,