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.

Post a Comment