Tuesday, 18 February 2014

Performing slow work outside the WPF UI thread and updating the WPF GUI afterwards

Developers using WPF are very familiar with the problem of doing slow work on the UI thread and trying to update the GUI thread afterwards. This is not only a problem in WPF, but also in Windows Forms and even web applications. If the UI thread is being kept busy with a heavy, synchronous calculation, other work such as resizing the window and even repainting the UI will suffer and this results in a non-responsive GUI that the user cannot work with. The solution is to do work in another thread or threads and update the GUI afterwards. To make this easier, I list up an extension method below to do this. The extension method is an extension method on the dispatcher, which will usually be this.Dispatcher in code behind or Dispatcher.CurrentDispatcher in MVVM scenarios and the user must supply the function that will return the thread work return a type T (this can be either an object or a list of objects for example) and also a gui update method that receives an object of type T that the thread work method calculated. The gui update method will usually specify what shall be performed in the GUI or in the ViewModel in MVVM scenarios, while the thread work method specifies first will specify how the object of type T is retrieved. It should be possible to use this extension method both in code-behind and MVVM scenarios.

public static class DispatcherUtil
    {

        public static void AsyncWorkAndUIThreadUpdate<T>(this Dispatcher currentDispatcher, Func<T> threadWork, Action<T> guiUpdate)
        {
            ThreadPool.QueueUserWorkItem(delegate(object state)
            {
                T resultAfterThreadWork = threadWork(); 
                currentDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action<T>(delegate(T result){
                    guiUpdate(resultAfterThreadWork);
                }), resultAfterThreadWork); 

            }); 
        }

    }

Next, an example of using this:

  private void Button_Click(object sender, RoutedEventArgs e)
        {

            this.Dispatcher.AsyncWorkAndUIThreadUpdate(() => FooService.GetSomeFooData(),
                s => btnFoo.Content = s); 

        }

The FooService is very simple and simulates a resource intensive calculation that takes about 5 seconds:

public static class FooService
    {

        public static string GetSomeFooData()
        {
            Thread.Sleep(5000);
            return "Hello world! "  + DateTime.Now.ToString(); 
        }

    }

I use ThreadPool.QueueUserWorkItem here. Of course this is a very simple example, and in complex applications one needs to watch out for state problems in the UI. When it is not sure when the calculation is performed, you should perhaps consider to avoid refreshing the UI if the UI has changed, especially in more complex MVVM component based WPF User Interfaces, based on for example Prism. At the same time, locking up the UI is not good either. I have only tested the code above in a simple scenario, but expect it to work also with more complex WPF UI. There are two golden rules when it comes to threading in WPF: 1. Never access UI elements from another thread than the UI thread 2. Do not do slow work on the UI thread but in another thread Most difficulties doing off thread async work in WPF comes to updating the UI after the calcuations are done in the other thread(s). With this extension method, hopefully WPF developers will find it easier.

4 comments:

  1. In MVVM scenarios, change the method above from an extension method to a static method. Pass in Dispatcher.CurrentDispatcher as the dispatcher. I have made this work in my WPF application which is based on Prism and MVVM. Works like a charm!

    ReplyDelete
  2. Did you know that you can generate dollars by locking special sections of your blog / site?
    Simply open an account with AdWorkMedia and use their content locking tool.

    ReplyDelete
  3. Looking for the Ultimate Dating Site? Create an account and find your perfect match.

    ReplyDelete
  4. Are you making money from your exclusive shared links?
    Did you know that ShareCash will pay you an average of $500 per 1,000 file downloads?

    ReplyDelete