Wednesday, December 21, 2011

WPF and data multithreading deadlocks.

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.

The call stack for the data thread.

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, which raises the PropertyChanged 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 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 property and wait for an acquire of the lock that the data thread is holding. The data thread then sets the DataObject.Timestamp property inside the critical region code and the PropertyChanged event is raised. This causes the new time stamp value to be marshaled back onto the Dispatcher with a blocking/synchronous Dispatcher.Invoke call. Although the data thread now deadlocks in the critical region, waiting for the UI Dispatcher.Invoke 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, override the OnPropertyChanged 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.

2 comments:

  1. There may be a deadlock, but the explanation isn't right. It's got nothing to do with Dispatcher.Invoke, which WPF databinding doesn't use in this scenario (or any other). It does have something to do with locks on a private data structure in WPF's weak-event delivery. The data thread callstack shows it's waiting for read-access, but someone already has write-access. There's not enough information here to determine who it is, or what it's waiting for.
    This doesn't seem to have been reported to MS. Please open a .Net Framework bug at http://connect.microsoft.com/VisualStudio, and attach a repro.

    ReplyDelete
    Replies
    1. Hi Sam, thanks for the comment. I removed the errors from the post. Could you elaborate more why you believe it is related to WPF's weak-event delivery?

      Unfortunately, the offending code to duplicate the issue is not under my domain anymore and I haven't tried reproducing it recently to verify it is still an issue.

      Delete