How to interact with a form from a background thread in WPF and VB.NET


Code (Photo credit: three_sixteen)

Let’s say you’re writing a program that does a lot of work. You don’t want to have an unresponsive user interface, and you want to have the ability to abort the work part way through. You have several options – using async versions of blocking methods, making async versions of your own blocking methods, or you could do all the work on a separate thread.

Let’s say you decided to do the work on a separate thread. This makes it easy to abort the work, because you can just abort the thread.  Now you want to be able to have the worker thread report its state and progress to the main thread that owns the UI.  An easy way of doing this is by having an object that represents the worker, and having that object raise events that your form can subscribe to.

Unfortunately if you run this code, you’ll get an InvalidOperationException saying “The calling thread cannot access this object because a different thread owns it.”  The problem you have is that when your worker object raises an event, even though the event handler is in the form object it’s actually still executing in the worker thread.  Confusing, I know – things like this are why most programs don’t take full advantage of modern multi-core processors.  There’s plenty of scope for language, framework and tool makers to improve the ease of use of asynchronous programming.

In WinForms you’d call invoke on the control, passing it a delegate function that makes the actual updates to the UI.  In WPF controls don’t have an invoke method, instead you use the Dispatcher property on the control and call invoke on that.

You still have to pass invoke a delegate function to do the update, which is a pain – your one line event handler is now two methods and a delegate.  Wouldn’t it be handy if you could use a lambda to get it all on one line again?  How about this:

Private Sub Worker_State(pS_State As String) Handles Worker.State
    Dispatcher.Invoke(New Action(Sub() lblCurrentState.Content = pS_State))
End Sub

Pow.  And as of Visual Basic 2010 you can also do multi-line lambdas, so you can do more complicated event handlers like so:

Private Sub Worker_Progress(pI_Progress As Integer, pI_Total As Integer) Handles Worker.Progress
    Dispatcher.Invoke(New Action(Sub()
                                     pgbState.Maximum = pI_Total
                                     pgbState.Value = pI_Progress
                                 End Sub))
End Sub

Use it in good health.


Apparently there’s an easier way to do this – use BackgroundWorker which supports progress events and cancellation without needing to jump through any Dispatcher hoops.