Friday, 16 November 2007

InvokeRequired for WPF

Those of you who develop multi-threaded Windows Forms client applications will be well aware of the Control.InvokeRequired  method needed every time you access a control from a thread other than the one on which it was created. Code such as the lines seen below, which seemed so arcane when you first encountered them, have now been committed to memory:

void finder_FoundFile(object sender, FoundFileEventArgs e)
{
   if (this.InvokeRequired)
   {
      this.BeginInvoke(new EventHandler<FoundFileEventArgs>(finder_FoundFile), new object[] { sender, e });
   }
   else
   {
      listBoxFiles.Items.Add(e.File);
   }
}

But what about the brave new world of WPF? Will we have to write the same code, or will WPF controls let us set their properties from any thread? Sadly not. We have to do a similar trick, this time using the Dispatcher object to invoke the method call on the correct thread:

void finder_FileFound(object sender, FileFoundEventArgs e)
{
    if (listBoxFiles.Dispatcher.Thread == Thread.CurrentThread)
    {
        listBoxFiles.Items.Add(e.File);
    }
    else
    {
        listBoxFiles.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
            new EventHandler<FileFoundEventArgs>(finder_FileFound), sender, new object[] { e } );
    }
}

In fact, its even worse than for Windows Forms, as the thread signature for Dispatcher.BeginInvoke is even more perplexing than the one for Control.BeginInvoke. You not only have to specify a priority, but also you have to split your parameters up into "first parameter" and "array of all other parameters". Very odd.

But what about data binding in WPF? Surely that would be the easy way round this mess. Bind the list box to an observable collection, and let your background thread change the collection. Unfortunately, the same problem awaits you. As soon as you add something to the observable collection an InvalidOperationException fires warning you that you are not on the correct Dispatcher thread.

Does that rule out all hope of databinding in a multi-threaded context? Thankfully not, as I discovered a helpful article from Beatriz Costa explaining a way to make an observable collection that performs the context switch for you. Its not exactly ideal, but at least it will get you up and running. Apparently MS are considering improving the situation for the next drop of WPF.

5 comments:

Lieven Maes said...

Hi Mark,

Thank you very much for this information. I'm a beginning C# programmer and I'm trying to get some sensors (they communicate through serial port) to trigger some cool graphic animations in wpf.

The first thing I wanted to do was to get the sensordata in a constantly updating textbox in a xaml form. I couldn't understand why the invoke method didn't work as it did in a normal form, but know I do!

Anonymous said...

I know you wrote this ages ago, but you don't actually need to create an object array when the method signature specifies params object[]. You can just pass a list of objects. I have no idea why they felt the need to create two method definitions when they could have just defined BeginInvoke(DispatcherPriority, Delegate, params object[]), but who am I to argue?

See http://msdn.microsoft.com/en-us/library/w5zay9db.aspx for details.

Steve Westbrook said...

Hi Mark,

You can actually use Dispatcher.CheckAccess() as a direct equivalent of InvokeRequired.

Anonymous said...

Thanks for this, it helped.

Alan Macdonald said...

Just to expand on what Steve Westbrook said, CheckAccess works but it doesn't show up on intellisense because the EditorBrowsable attribute for some reason has been set to never. See http://social.msdn.microsoft.com/forums/en-US/wpf/thread/360540eb-d756-4434-86f9-a3449f05eb55