Useful WPF Threading Extension Method

If you are working with WinForms or WPF you will more than likely run into some long running operation that you would like to run in a new thread. A novice may actually try and create a new Thread, which is ok, but that means you are responsible for the entire lifecycle of your new thread. Which gets tricky.

A better approach would be to use the ThreadPool or use a BackgroundWorker component which uses the ThreadPool beneath the surface.

However, even using these approaches the cardinal rule is that the control is owned by 1 thread, the thread that created the controls. That is typically the UI thread. So when you try and update the controls from a background thread you will run into problems.

This code demonstrates the problem with cross thread calls :

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Windows;
   6:  using System.Windows.Controls;
   7:  using System.Windows.Data;
   8:  using System.Windows.Documents;
   9:  using System.Windows.Input;
  10:  using System.Windows.Media;
  11:  using System.Windows.Media.Imaging;
  12:  using System.Windows.Navigation;
  13:  using System.Windows.Shapes;
  14:  using System.ComponentModel;
  15:  using System.Windows.Threading;
  16:  
  17:  namespace BackgroundThread
  18:  {
  19:  
  20:  
  21:      public partial class Window1 : Window
  22:      {
  23:          private Int32 currentCount = 0;
  24:          private Int32 maxCount = 500;
  25:          private float factor = 0;
  26:  
  27:          public Window1()
  28:          {
  29:              InitializeComponent();
  30:  
  31:          }
  32:  
  33:          private void btnGo_Click(object sender, RoutedEventArgs e)
  34:          {
  35:              factor = (float)100 / maxCount;
  36:  
  37:              BackgroundWorker bgWorker = new BackgroundWorker();
  38:              bgWorker.WorkerReportsProgress = true;
  39:              bgWorker.WorkerSupportsCancellation = false;
  40:  
  41:              //DoWork
  42:              bgWorker.DoWork += (s2, e2) =>
  43:              {
  44:                  for (currentCount = 0;
  45:                      currentCount < maxCount; currentCount++)
  46:                  {
  47:                      lstItems.Items.Add(
  48:                          String.Format("Count {0}", currentCount));
  49:                  }
  50:              };
  51:  
  52:              //ProgressChanged
  53:              bgWorker.ProgressChanged += (s3, e3) =>
  54:              {
  55:                  pgbar.Value = e3.ProgressPercentage;
  56:              };
  57:  
  58:              bgWorker.RunWorkerAsync();
  59:  
  60:          }
  61:      }
  62:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Which when run will result in the following:

crossThread

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

So how can we fix this, well we could use the Dispatcher.Invoke around the offending items, but perhaps a more elegant solution may be to use a extension method.

   1:  public static class WPFThreadingExtensions
   2:  {
   3:      /// <summary>
   4:      /// Simple helper extension method to marshall to correct
   5:      /// thread if its required
   6:      /// </summary>
   7:      /// <param name="control">The source control</param>
   8:      /// <param name="methodcall">The method to call</param>
   9:      /// <param name="priorityForCall">The thread priority</param>
  10:      public static void InvokeIfRequired(
  11:          this DispatcherObject control,
  12:          Action methodcall,
  13:          DispatcherPriority priorityForCall)
  14:      {
  15:          //see if we need to Invoke call to Dispatcher thread
  16:          if (control.Dispatcher.Thread != Thread.CurrentThread)
  17:              control.Dispatcher.Invoke(priorityForCall, methodcall);
  18:          else
  19:              methodcall();
  20:      }
  21:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Which we can then use in our code as simply as follows :

   1:      factor = (float)100 / maxCount;
   2:  
   3:      BackgroundWorker bgWorker = new BackgroundWorker();
   4:      bgWorker.WorkerReportsProgress = true;
   5:      bgWorker.WorkerSupportsCancellation = false;
   6:  
   7:      //DoWork
   8:      bgWorker.DoWork += (s2, e2) =>
   9:      {
  10:          for (currentCount = 0;
  11:              currentCount < maxCount; currentCount++)
  12:          {
  13:  
  14:              this.InvokeIfRequired(() =>
  15:              {
  16:                  lstItems.Items.Add(
  17:                      String.Format("Count {0}", currentCount));
  18:              },
  19:                  DispatcherPriority.Background);
  20:  
  21:              bgWorker.ReportProgress((int)(factor * (currentCount + 1)));
  22:  
  23:          }
  24:      };
  25:  
  26:      //ProgressChanged
  27:      bgWorker.ProgressChanged += (s3, e3) =>
  28:      {
  29:          this.InvokeIfRequired(() =>
  30:          {
  31:              pgbar.Value = e3.ProgressPercentage;
  32:          },
  33:          DispatcherPriority.Background);
  34:  
  35:  
  36:      };
  37:  
  38:      bgWorker.RunWorkerAsync();
  39:  
  40:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Which when run allows cross threaded calls to be marshaled to the correct Dispatcher object.

better

Hope this helps.
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Advertisements

26 thoughts on “Useful WPF Threading Extension Method

  1. sacha says:

    Thanks Josh.

  2. […]  [UPDATE] Sacha Barber has a excellent new post about BackgoundWorker + WPF, read more here […]

  3. Bruno Martínez says:

    I don’t think there’s an advantage over using the Dispatcher directly. Look at

    internal object InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, object args, bool isSingleParameter)

    in Reflector. The case in which the dispatcher belongs to the current thread is already handled.

  4. sacha says:

    Thats true, but there is an overhead in calling Invoke so you should not do so unless you have to.

  5. […] Useful WPF Threading Extension Method (Sacha Barber) […]

  6. Your extension method is cool, but perhaps is better used only with winforms? I find the use case for your method questionable with WPF best practices. If you use a Model-View-ViewModel architecture you would never interact with the view directly; rather the view-model.

  7. sacha says:

    Actually the same applies even using the MVVM pattern, which we use all the time on my current project. You are still having to marshall stuff back to the UI thread from time to time, as we are calling sessionful WCF services that sometimes use different threads, so they data that comes back MUST be marshaled back to the UI thread.

    See what I mean.

    Yoiu should read up on the MVPoo pattern, Dr WPF talks about it a bit, and I am lucky enough to be part of a very good WPF group called the WPF Disciples that have many Microsoft Architects and Program managers and tool creators in it, and we all agree no matter how much you try there will always be something trying to make your pure pattern a bit stinky with some UI poo in there somewhere.

  8. Miguel says:

    Ey, great job.

    Thank you.

  9. sacha says:

    Thanks man

  10. Kevin says:

    Nice post!

    In response to Richard’s comment, another technique is to use a DelegateMarshaler from this article:

    http://blog.quantumbitdesigns.com/2008/07/22/delegatemarshaler-replace-controlinvokerequired-and-controlinvoke/

    That way Models and/or ViewModels do not need references to any ‘controls.’ I, like Richard, do not like any interaction with ‘views’ in my VM layer.

    This is example usage:
    marshaler.Invoke(ShowDownloadComplete, fileName);

    BTW, the blog post formatting was lost in a crash, I need to fix it some day.

    Kevin

  11. sacha says:

    Kevin

    Thanks for the link, however I am not sure this would work with WPF as there is no SynchronizationContext to speak of, the Dispatcher deals with it.

    I could be wrong, but I feel what I say is correct.
    What you show is ace for Winforms though.

  12. Kevin says:

    WPF has a DispatcherSynchronizationContext that gets loaded for WPF apps when Dispatcher.Run() is called (which is called eventually by Application.Run())

    Therefore, WPF ViewModels do not need window references, which I love.

  13. sacha says:

    Cool thanks for that Kevin, I was not actually aware of the DispatcherSynchronizationContext class

  14. Jasvinder says:

    Nice post Sacha.I Like your way of coding.

  15. sacha says:

    Thanks Jasvinder

  16. Yazid says:

    Sacha

    Great article, by any chance do you an example which combines the technique you describe here and MVVM

    TIA
    Yaz

  17. dumbledad says:

    Great post, pithy and useful. Thanks. What are you doing if the invoked method call has arguments so that the ‘Action methodcall’ argument in your extension method is inadequate?

  18. dumbledad says:

    But isn’t Action for parameterless calls?

  19. Brian Winkleman says:

    What would be really neat is an example of a background worker against a database that you can actually cancel. Oracle database that is. Just would like to be able to kill a long running query. The users today close the application and open it again.

    thanks

    • sacha says:

      That would be quite hard, as background threads are normally started on UI, and actually done on server. So the call to the server could be cancelled, but once its started the DB call, how do you cancel it. Thats the bit that takes the time actually.

  20. MLL says:

    Excellent info for this particular scenario. Do you have any advice for this one?

    WPF+VB.NET

    Concept:
    Pure custom controls where -any- of the properties may be hit by any number of threads. The UI style(s) use TemplateBinding against the relevant properties. I have used safe threading extensively for years both in Win Forms and now in WPF. But in this case I’m looking for a more elegant solution than having to create a batch of delegates if possible.

    Obviously I can’t use anon delegates yet (/me sniffs) and I would very much like to avoid having to create a slew of delegates if possible. I could probably manage this by using reflection / other techniques to dynamically build one for the call but then the overhead of that would get out of hand pretty fast.

    My current understanding is we can not use the Lambda Function() for it because.. that’s not what it’s for. It’s a function and therefore must return a value and can not be used to simulate an anon delegate sub in this case.

    Thanks for any advice or links to possible solutions.

    PS: An answer of “use C#?” won’t solve the problem 🙂

    • sacha says:

      Pure custom controls where -any- of the properties may be hit by any number of threads

      Mmmm that would be hard, as you can never get out of having to marshall to UI thread if you have to do it, you have to do it.

      As for an elegant technique, I’m afraid not. As for Lamdas, they do not always have to return a value, for example I could have something like this

      Button.Click += (s,e) => { MessageBox.Show(“Clicked”); };

      That does not return anything, but its a lambda.

      I am afraid it all marshalling hard work for you.

  21. MLL says:

    Yeah that’s what I assumed but wanted to confirm it before I go down that road if I didn’t have/need to.

    I know I can not escape the requirement to marshal calls to the owning thread that is a given (and I actually understand why, and have for a long time). I’m simply wanting to do it in a more generic way than having to explicitly define delegates.

    Yep I’ve tried an approach such as this with Lambda but the call is never actually made, though it does process it without complaint:

    Application.Current.Dispatcher.Invoke(Function() (Message = value))

    However in this case I believe it is actually evaluating it as an expression testing if Message = Value instead of setting it.

    I appreciate your response thanks!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: