Freigeben über


Multi-threading your UI

Basically, window app UI, either WPF or traditional WinForm, is single threaded, which means only one thing can happen in the UI at any given time. To be specific, it is not generally possible to create an object on one thread, and access it from another. In almost all cases this will result in an InvalidOperationException stating that “The calling thread cannot access this object because a different thread owns it.” On the other hand, we can’t do everything in the UI for many reasons such as performance requirement, UX responsiveness needs or testability concerns. One typical scenario justifying this is as follows. You are connecting to a remote server in background thread, and you might also want users to know the connectivity status after a while. So this leads to the general question: How to mul-thread UI? In this post, I will talk about both WPF and Win Form because Win Form has not gone yet. J

Technique #1: Dispatch a delegate on UI thread

Use Dispatcher.Invoke, we can execute the specified delegate on UI thread. The sample code would be:

/// WPF

private void OnConnectivityChanged(object sender, ConnStatusInfoEventArgs statusInformation)

{

     this.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() =>

     {

           this.MessageTextBlock.Text = statusInformation.msg;

     }));

}

this is the form object or control object owned by the UI thread. In this event handler, we simply call the Invoke method. The first parameter is priority; the second one should a delegate. In our case we cast a statement lambda to Action delegate type. Other than invoke(), you can also use methods such as BeginInvoke() in certain situations. And the equivalent code for WinForm would be:

/// WinForm

private void OnConnectivityChanged(object sender, ConnStatusInfoEventArgs statusInformation)

{

     this.Invoke(new System.Windows.Forms.MethodInvoker(delegate()

     {

           this.MessageTextBlock.Text = statusInformation.msg;

     }));

}

We are using an anonymous delegate in above implementation, and you can see the code is pretty similar.

Technique #2: Update the UI while application is not busy

WinForm is still core part of many Windows application UI, especially when people design software years ago. For example, to extend the Outlook 2007 functionalities by writing your own form region in an add-in, you have to follow WinForm principles. To update your own form region in main Outlook UI thread, one of simple ways is to update it when application is idle:

/// WinForm

System.Windows.Forms.Application.Idle += new EventHandler(UpdateMyUI_OnApplicationIdle);

In WPF world, this can be achieved by using DispatcherPriority.ApplicationIdle as dispatch priority parameter in technique #1.

Technique #3: Background Worker

Background worker is the build-in .NET mechanism to multi-thread UI, but the reasons why I list it as #3 are that developers complain that it is not that good to fit all scenarios as opposed to your own thread. If you take a look at exposed events by background worker, you will know why. You can find sample code here if you are still interested in.

Technique #4: Freezable (WPF specific)

In WPF, freezable objects can be frozen, at which point they become read-only and can be used on any thread at any time. These are used across all graphics primitive resources (brushes, pens, etc).

if (myBrush.CanFreeze)

{

       // Make the brush unmodifiable

       myBrush.Freeze();

       // From now on, this frozen freezable object(brush) can be shared across threads

       // DoOtherStuff();

}

You can also create your own freezable classes.

Technique #5: HostVisual (WPF specific)

If you doesn’t require interactivity(e.g., keyboard inputs), HostVisual could be another option WPF provides. The element tree owned by the worker threads are rendered into their own composition target (called VisualTarget), and the results are composed into the HostVisual owned by the UI thread. The excellent code sample I found is here.

Comments