WPF UI Update from Background Threads

I'm trying to do my apps in any type of XAML these days since I finally think I "got it". I wish I could claim that I will break free from the limitations of HTML and my new found liberty will result in fantastic looking apps -- but in reality, I still lack the design skills to come up with something that looks really cool.

Either way, XAML presents a great opportunity to really innovate on the UIs with regards to looks and interactivity, regardless if you’re writing desktop apps with WPF, web apps with Silverlight, mobile apps with Silverlight Mobile or if you're programming for one of these cool Surface tables ... and I will write my puny little test apps in WPF, even if they are just as ugly as my Windows Forms apps used to be.

Now, I'm writing this cool little app and I want to do some interesting stuff in the background (my apps may be ugly, but at least they are responsive). WPF is still STA, which means you still have to be explicit about posting back to the main UI thread if your background thread needs to communicate updates … bummer, but I thought I knew how to program async from my Windows Forms apps. As it turns out, it's a little bit different and there are few things to keep in mind. It also turns out that things are still not as widely document. That’s why I’m sharing.

For starters, there's the handy Dispatcher on every UIElement which provides the BeginInvoke method to run code on the right thread.

For example, my program dispays progress with a WPF ProgressBar object. To update that ProgressBar from the background thread, the ProgressBar fires an event. The EventHandler then calls BeginInvoke, on the background thread. BeginInvoke will then pass the DispatcherOperationCallback to the main thread to execute:

 

runner.RequestFinished += delegate(object s, RequestFinishedEventArgs e2)
{
    System.Diagnostics.Debug.WriteLine("Update event handler called.");
    try
    {
        progressBar1.Dispatcher.BeginInvoke(
           System.Windows.Threading.DispatcherPriority.Normal
           , new DispatcherOperationCallback(delegate
                   {
                       System.Diagnostics.Debug.WriteLine("value is: " + progressBar1.Value);
                       progressBar1.Value = progressBar1.Value + 1;
                       return null;
                   }), null);
 
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine(ex.ToString());
 
    }
};

Works great - you see the progressBar update every time the event handler is invoked. I had some Debug.WriteLine statements in there to make sure:

Update event handler called.

value is: 0 <—delegate being called to update the UI

Update event handler called.

value is: 1 <—delegate being called to update the UI

It works great … unless you're working with synchronization objects like ManualResetEvent. My code was waiting *on the background thread* for my stuff to complete, while at the same time posting back to the main thread with BeginInvoke - under the assumption that only the background thread is blocking:

ManualResetEvent waiter;
 
public TestRunner()
{
    waiter = new ManualResetEvent(false);
}
 
public void RunAsync(TestConfig conf)
{
    Thread t = new Thread(RunInternal);
    t.IsBackground = true;
    conf.wait = waiter;
    waiter.Reset();
    t.Start(conf);
    waiter.WaitOne();
    System.Diagnostics.Debug.WriteLine("done waiting");
}

WRONG assumption. The background thread is executing until the ManualResetEvent's WaitOne() returns. All the messages from the Dispatcher are queued up. Consequently, the progressBar doesn't update even though the background thread is operating as expected and the Debug Output looks like this:

Update event handler called.

<—delegate NOT called to update the UI

Update event handler called.

<—delegate NOT called to update the UI

done waiting <—delegate NOT called until the ManualResetEvent was Set()

value is: 0

value is: 1

The best way around this is to architect your program event driven, for example as a state machine, and avoid synchronization objects altogether.

Hopefully you enjoy UI programming with WPF or Silverlight as much as I do.

8 Comments

  • Hi Christoph,

    Whilst you rightly point out the usefulness of the Dispatcher object that is available on any UIElement, I think the BackgroundWorker class is probably more useful for the kind of tasks that you are describing, especially for updating progress.

    You define the task that the BackgroundWorker will perform in the DoWork event handler, set WorkerReportsProgress to true, and then handle the task completion in the RunWorkerCompleted event handler. In the DoWork event handler you can call ReportProgress (which takes an int for the percent complete and an optional object of additional information), which results in the ProgressChanged event being fired so that you can update the UI. The BackgroundWorker marshals these event handlers onto the thread that created it, so as long as you create your BackgroundWorker on the UI thread, you'll always be able to update the UI from your BackgroundWorker.

    Hope this helps,

    Derek.

  • Is it possible to give a VB.net example of the above please.

  • I wish they'd make this stuff easier to use.

  • Hello, i read your blog out of time to time and i own a
    similar one and i was just wondering whenever you
    discover plenty of spam responses If so how do you protect against it, any plugin or
    anything you can still advise I find out so much lately it's driving me crazy so any help is very much appreciated.

  • Merely several blogger would discuss this topic the
    tactic you do.*:.~;

  • I absolutely love your blog and find the majority of your post's to be exactly I'm looking for.

    Do you offer guest writers to write content available for you?
    I wouldn't mind creating a post or elaborating on some of the subjects you write regarding here. Again, awesome web site!

  • It's very simple to find out any matter on web as compared to books, as I found this post at this web page.

  • Wow that was strange. I just wrote an very long comment but after I clicked submit my comment didn't show up. Grrrr... well I'm not writing all that over again.
    Regardless, just wanted to say fantastic blog!

Comments have been disabled for this content.