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.