BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Windows Presentation Foundation
.NET 4.5+

WPF Base Classes - DispatcherObject

The eighteenth part of the Windows Presentation Foundation Fundamentals tutorial describes the DispatcherObject class, which is a base class for all of the WPF controls. This class can be used to correctly access control properties from parallel code.

Dispatchers

WPF controls have thread affinity. In simple terms, this means that the thread that creates the control in the first instance is the only thread that is permitted to access it. If you attempt to read or modify a control's properties from another thread, you will encounter an InvalidOperationException. The exception's message is, "The calling thread cannot access this object because a different thread owns it."

If your project is simple and does not employ multithreading or parallel programming, you should never see this exception, as you'll only ever run code on the user interface (UI) thread. This is the same thread that will be used to create the controls so no illegal cross-threading operations will occur. However, single-threaded code does not take advantage of all of the available processor cores and can lead to poor responsiveness.

To deal with the cross-threading issue, all WPF controls are linked to a dispatcher. A dispatcher is responsible for managing the work items for a thread. If you can access the dispatcher for a control that you wish to modify from another thread, you can use it to invoke operations against that control safely. Luckily, all WPF controls inherit functionality from a type that gives access to the appropriate dispatcher. The base class in question is DispatcherObject, which is found in the System.Windows.Threading namespace.

DispatcherObject

The key property of DispatcherObject is Dispatcher. This returns an object, of the Dispatcher type, that is linked to the thread that created the control. You can use this object to safely access the control.

To demonstrate, let's first create a project that includes an illegal cross-thread call. Create a new WPF application project in Visual Studio, naming the project, "DispatcherObjectDemo". Once the project is initialised, replace the XAML in the automatically generated window with that shown below:

<Window x:Class="DispatcherObjectDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="DispatcherObject Demo" Width="300"
        Height="250">
    <UniformGrid Name="MyGrid" Rows="1">
        <Button Width="100" Height="50" Click="Parallel">Parallel</Button>
        <Button Width="100" Height="50" Click="Standard">Standard</Button>
    </UniformGrid>
</Window>

The XAML defines a UniformGrid that contains two buttons. Each references a Click event for which we need to create some code. Switch to the code for the window and add the following methods:

private void Parallel(object sender, RoutedEventArgs e)
{
    Task.Run(() => MyGrid.Background = Brushes.Red);
}

private void Standard(object sender, RoutedEventArgs e)
{
    MyGrid.Background = Brushes.Green;
}

The methods seem innocuous enough. Both simply change the background colour of the UniformGrid. The difference between the two, other than the choice of colour, is that the Parallel method changes the colour within a parallel task. This would give a more responsive user interface if the process took a long time to run.

Try running the program in the debugger. You should find that clicking the "Standard" button changes the background colour as expected. However, clicking the "Parallel" button causes an exception because the parallel task does not run on the thread that created the grid.

Using the Dispatcher

Fixing the Parallel method is simple. We know that the code will be running separately to the UI thread. We therefore need to obtain a reference to the UI thread's dispatcher and use this to run the code to update the control. As mentioned previously, all controls provide this reference via their Dispatcher property.

Once we have a Dispatcher reference, we can call synchronous code on its thread using the Invoke method. There are several overloaded versions of this method but we'll use just one in this article. The overload has a single parameter that receives a delegate to execute. We'll use an Action delegate, defined with a lambda expression.

Update the Parallel method as shown below. Note that the property change is now performed in a lambda expression, which is executed using the Invoke method of the UniformGrid's Dispatcher.

private void Parallel(object sender, RoutedEventArgs e)
{
    Task.Run(() =>
    {
        MyGrid.Dispatcher.Invoke(() => MyGrid.Background = Brushes.Red);
    });
}

Try running the program again. You should find that both buttons now function correctly.

NB: In most cases all controls are created from the UI thread. This means that you can obtain a Dispatcher from any control and make changes to several controls within the same call to Invoke. If you create controls from multiple threads you need to be more selective when invoking actions using their dispatchers.

Checking for Access

Sometimes you will write reusable methods to update controls. These may be called from the UI thread or from parallel tasks. When running code on the same thread as the controls being affected, there is no benefit in accessing the Dispatcher. Indeed, there may be a small performance penalty.

You can check whether the current thread is permitted to access a control's properties using the CheckAccess method. If the target control was created on the current thread the method returns true. If not, it returns false. You might decide to use this method before deciding whether to use a dispatcher or to modify a control directly.

To demonstrate, replace the Parallel and Standard methods with the code shown below. Here both buttons call a shared method that changes the grid's colour. The ChangeColour method calls the grid's CheckAccess method to determine whether the dispatcher must be used to update the Background property.

private void Parallel(object sender, RoutedEventArgs e)
{
    Task.Run(() => ChangeColour(Brushes.Red));
}

private void Standard(object sender, RoutedEventArgs e)
{
    ChangeColour(Brushes.Green);
}

private void ChangeColour(Brush brush)
{
    if (MyGrid.CheckAccess())
        MyGrid.Background = brush;
    else
        MyGrid.Dispatcher.Invoke(() => { MyGrid.Background = brush; });
}
21 June 2013