Click here to Skip to main content
15,885,244 members
Articles / Desktop Programming / WPF
Tip/Trick

UiDispatcherHelper - Dispatcher Helper Class for UI Bound Actions in WPF

Rate me:
Please Sign up or sign in to vote.
3.73/5 (9 votes)
10 Apr 2017CPOL3 min read 15.2K   6   8
This is a helper/utility class that will add a layer of abstraction as well as separation for dispatcher related operations in WPF.

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.

C#
public static class UiDispatcherHelper
  {
      public static Dispatcher UiDispatcher { get; private set; }

      /// <summary>
      /// This method should be called once on the UI thread to ensure that
      /// the property is initialized.
      /// <para>In WPF, call this method on the static App() constructor.</para>
      /// </summary>
      public static void Initialize()
      {
          if (UiDispatcher != null && UiDispatcher.Thread.IsAlive)
              return;

          UiDispatcher = Dispatcher.CurrentDispatcher;
      }

      /// <summary>
      /// Executes an action on the UI thread.
      /// The action will be enqueued on the UI thread's
      /// dispatcher and executed asynchronously.
      /// </summary>
      /// <param name="action">The action will
      /// be enqueued on the UI thread's dispatcher
      /// and executed asynchronously..
      /// </param>
      public static void BeginInvokeOnUi(Action action)
      {
          if (action == null)
          {
              throw new ArgumentNullException("action");
          }

          UiDispatcher.InvokeAsync(action, DispatcherPriority.Input);
      }

      /// <summary>
      /// Executes an action on the UI thread. The action will be enqueued
      /// on the UI thread's dispatcher and executed synchronously.
      /// </summary>
      /// <param name="action">
      /// The action that will be executed on the UI thread synchronously.
      /// </param>
      public static void InvokeOnUi(Action action)
      {
          if (action == null)
          {
              throw new ArgumentNullException("action");
          }

          UiDispatcher.Invoke(action);
      }

      /// <summary>
      /// Executes an action on the UI thread. The action will be enqueued on the
      /// UI thread's dispatcher with the specified priority and executed asynchronously.
      /// </summary>
      /// <param name="action">
      /// The action that will be executed on the UI thread.</param>
      /// <param name="priority"></param>

      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.

C#
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.

C#
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:

C#
private static void ShowMessageBox(string msgHeader, string message, EMessageBoxType eMessageBoxType)
        {
            UiDispatcherHelper.InvokeOnUi(() =>
            {
               // code that you want execute on UI thread.
            });
        }

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:

C#
var response = Task.Run(request);
response.ContinueWith(x => UiDispatcherHelper.BeginInvokeOnUi(() =>
            {
              // code that you want execute on UI thread.
            });

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.

C#
[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:

C#
protected void WaitForThreads()
{
    int maxThreads = 0;
    int placeHolder = 0;
    int availThreads = 0;
    int timeOutSeconds = 10;
    
    //Now wait until all threads from the Threadpool have returned
    while (timeOutSeconds > 0)
    {
        //figure out what the max worker thread count it
        System.Threading.ThreadPool.GetMaxThreads(out 
                     maxThreads, out placeHolder);
        System.Threading.ThreadPool.GetAvailableThreads(out availThreads,
                                                       out placeHolder);
                                                       
        if (availThreads == maxThreads) break;
        // Sleep
        System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(1000));
        --timeOutSeconds;
    }
    // You can add logic here to log timeouts
}

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".

C#
[TestMethod]
public void Save_Click()
{
    //Unit test set-up operations
    
    WaitForThreads();
    UiDispatcherHelper.UiDispatcher.ShutdownFinished += (sender, args) =>
    {
        //Check for the success of the operation and assertion
        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,

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
India India
Expertise in WPF/ Silverlight application design, Integration of WPF and MFC components for windows based application, Using MEF/Unity for creating decoupled components, using TPL for Parallel Programming, Implementing various Design Patterns for a manageable and scalable solution.

Comments and Discussions

 
QuestionScreen freeze Pin
Member 112436654-Jan-18 4:51
Member 112436654-Jan-18 4:51 
QuestionSource code Pin
Member 112436654-Jan-18 4:05
Member 112436654-Jan-18 4:05 
GeneralMy vote of 5 Pin
Member 112436654-Jan-18 4:00
Member 112436654-Jan-18 4:00 
QuestionUseful Pin
wow198619-Apr-17 6:06
professionalwow198619-Apr-17 6:06 
GeneralFYI Cut paste error Pin
BryanWilkins7-Apr-17 7:13
professionalBryanWilkins7-Apr-17 7:13 
GeneralRe: FYI Cut paste error Pin
Swapnil Dhage10-Apr-17 0:21
Swapnil Dhage10-Apr-17 0:21 
GeneralRe: FYI Cut paste error Pin
BryanWilkins9-May-17 6:32
professionalBryanWilkins9-May-17 6:32 
GeneralRe: FYI Cut paste error Pin
Swapnil Dhage23-Aug-17 2:55
Swapnil Dhage23-Aug-17 2:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.