We wanted to check whether WPF data binding is thread safe, and made a little test (using .NET 3.5). Lets look at the Worker class, which has one property - Money, and is making money in a multithreaded way :) - creating 100 threads and increasing the “Money” property from each one of them:
class Worker : INotifyPropertyChanged { private int money = 0; private object lockObj = new object(); private Dispatcher dispatcher = Dispatcher.CurrentDispatcher; protected void OnPropertyChanged(string propertyName) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } public int Money { get { if (dispatcher.Thread.ManagedThreadId != Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Dispatcher.CurrentDispatcher.Thread.ManagedThreadId) { Console.WriteLine(“{0},{1}”, Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â dispatcher.Thread.ManagedThreadId , Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Dispatcher.CurrentDispatcher.Thread.ManagedThreadId); } return money; } private set { money = value; OnPropertyChanged(“Money”);} } public void Work() { for (int i = 0; i < 100; i++) { new Thread(new ThreadStart(DoSomethingGood)).Start(); } } private void DoSomethingGood() { for (int i = 0; i < 100; i++) { Thread.Sleep(10); lock (lockObj) { Money = money + 1; } } } #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged = delegate { }; #endregion }
Now, in the main window of the application, create a Worker instance. set it as the DataContext and bind a TextBlock to its Money property. When activating the Work method the worker creates 100 threads increasing the Money property and the PropertyChanged event from each of them. Dispatcher instance is saved when the object is created so I can compare the thread id each time the Money property is accessed. The assumption here is that the Worker is created on the GUI thread.
Running this test I notice that the get_Money is always accessed from the GUI thread - so the framework already takes care of multi threading binding and every thing works!
Now lets see what happens if you try to hook up to the property changed event and change the GUI yourself. I added the handler like this:
worker.PropertyChanged += worker_PropertyChanged; ... void worker_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == “Money”) { moneyTextBlock.Text = worker.Money.ToString(); } }
As expected, the exception is thrown: “The calling thread cannot access this object because a different thread owns it.”
Lets solve this in the Worker class, exposing thread safe property changed event. change the OnPropertyChanged code like this:
protected void OnPropertyChanged(string propertyName) { if (dispatcher.Thread == Thread.CurrentThread) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } else { dispatcher.BeginInvoke(DispatcherPriority.DataBind, (ThreadStart)delegate() { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); }); } }
Now the Worker class is safe, whether one use it with data binding or not. No doubt data binding is better, but if you can’t use it for some reason, you can still use the Worker to make you money…
By Will on Apr 14, 2008 | Reply
Nice. I’m working up to doing this in a side project, so I’m glad to see DB’ing is thread safe.
BTW, no need to cast the delegate to a ThreadStart; its done for you by the compiler.