2 minute read

WPF & Multithreading

In designing applications, it may be advantageous to run data processing methods on a separate thread from the UI. This allows greater responsiveness in the application and takes advantage of concurrent computation.

The cornerstone interface to any binding in WPF is INotifyPropertyChanged, which is implemented on a data structure's different properties. Although this can be the source of deadlocks if the data structure is being processed on a separate thread where locks are explicitly implemented by the developer.

A deadlock could occur if some UI event was being triggered on a data structure (eg. expanding a view, changing a text box entry) while the data thread was processing it with an acquired lock. Internally, the PropertyChanged event will call a Dispatcher.Invoke on the UI. Although the UI could be waiting for the lock to release on that same property that the data thread already has, creating a cyclic dependency or deadlock.

The call stack for the UI thread. Note the wait call.

::: {.separator style=”clear: both; text-align: center;”} :::

The call stack for the data thread.

::: {.separator style=”clear: both; text-align: center;”} :::

Deadlock Scenario

As an example, suppose you have your standard WPF application UI thread and a separate data thread. The data thread polls a time stamp from a remote database and updates a class property called DataObject.Timestamp{.inlinecode}, which raises the PropertyChanged{.inlinecode} event. The property is also bound to a text box in our WPF application window, which is made visible in the window based on the user's preference (ie. collapsing and expanding a view).

In our deadlock example, the data thread puts a lock on the DataObject.Timestamp{.inlinecode} property while it updates it and other data structures. While this is happening, the view is expanded, causing the text box to start binding to the DataObject.Timestamp{.inlinecode} property and wait for an acquire of the lock that the data thread is holding. The data thread then sets the DataObject.Timestamp{.inlinecode} property inside the critical region code and the PropertyChanged{.inlinecode} event is raised. This causes the new time stamp value to be marshaled back onto the Dispatcher{.inlinecode} with a blocking/synchronous Dispatcher.Invoke{.inlinecode} call. Although the data thread now deadlocks in the critical region, waiting for the UI Dispatcher.Invoke{.inlinecode} to return, which is waiting on the data thread to release the lock.

Solution

To prevent the data thread's deadlock critical region from blocking on the Dispatcher.Invoke{.inlinecode}, override the OnPropertyChanged{.inlinecode} method to marshal the value asynchronously to the dispatcher.

protected override void OnPropertyChanged(string propertyName)
{
 Application.Current.Dispatcher.BeginInvoke(new Action(() => { base.OnPropertyChanged(propertyName); }));
}

This will ensure that the critical region doesn't block and the property changes are still serviced by the dispatcher.

Leave a comment