简体   繁体   中英

C# thread safety (in particular MVVM/WPF)

I'm wondering what I need to do to make models thread safe in MVVM. Say I had the following class, which is instantiated as a singleton:

public class RunningTotal: INotifyPropertyChange
{
   private int _total;
   public int Total
   {
      get { return _total; }
      set
      {
         _total = value;
         PropertyChanged("Total");
      }
   }
   ...etc...
}

My view model exposes it via a property:

public RunningTotal RunningTotal { get; }

And my view has a textblock bound to it, ie {Binding Path=RunningTotal.Total} .

My app has a background thread that periodically updates the value of Total. Assuming nothing else updates Total, what (if anything) should I do to make all this thread-safe?

Now, what if I wanted to do something similar but using a property of type Dictionary<> , or ObservableCollection<> ? Which members (add, remove, clear, indexer) are thread-safe? Should I use a ConcurrentDictionary instead?

My app has a background thread that periodically updates the value of Total. Assuming nothing else updates Total, what (if anything) should I do to make all this thread-safe?

For scalar properties, you don't need to do anything special; the PropertyChanged event is automatically marshaled to the UI thread.

Now, what if I wanted to do something similar but using a property of type Dictionary<>, or ObservableCollection<>? Which members (add, remove, clear, indexer) are thread-safe? Should I use a ConcurrentDictionary instead?

No, this is not thread-safe. If you change the content of an ObservableCollection<T> from a background thread, it will break. You need to do it on the UI thread. An easy way to do it is to use a collection that raises its events on the UI thread, like the one described here .

As for Dictionary<TKey, TValue> , it doesn't raise a notification when its content changes, so the UI is not notified anyway.

Say that there are two threads updating Total , and you want to log all changes to _total in the PropertyChanged method. Now there is a race condition in which PropertyChanged can miss a value. This happens when a thread blocks in the middle of calling set_Total . It updates _total but does yet call PropertyChanged. In the meantime, another thread updates _total to another value:

thread1: _total = 4;
thread2: _total = 5;
thread2: PropertyChanged("Total");
thread1: PropertyChanged("Total");

Now, PropertyChanged is never called with the value of 4.

You can solve this by passing the value to the PropertyChanged method, or by using a lock in the setter.

Since you say that you have one thread which updates this property, there is no possibility for a race condition. This is only the case when multiple threads (or processes) update the same thing at the same time.

The model should be written in a thread-safe way just like any code must be; it's up to you to determine whether you are using locks, concurrent containers or anything other to do it. The models are just library code, which (almost) shouldn't know that its functionality is going to be consumed by a MVVM application.

The VMs, however, have to work in the UI thread. This means that typically they cannot rely that the events from model are coming in the UI thread, so they have to marshal the calls or store them in a task queue if the subscribed events are coming not at the UI thread.

So, the VM is the place which should care about thread safety in a specific way, more than the model needs to.

The View code, in turn, can usually live in happy ignorance about all the threading issues: it gets all the messages/calls/events/whatever in the dedicated UI thread, and makes it own calls in the UI thread as well.


Specifically for your case, your code is not the model but VM, right? In this case you have to fire your events in the UI thread, otherwise the View will be unhappy.

This question provides a thread-marshalled version of the ObservableCollection

How do you correctly update a databound datagridview from a background thread

However, you still need to worry about contention between threads, which would require you to lock resources when they are updated, or use something like Interlocked.Increment.

If one thread is updating, and another is reading, there exists the possiblity that a read is done half way through an update (For example, an Int64 is being modified. The first half (32 bits) has been updated in one thread, and before the second half has been updated, the value is read from a second thread. A completely wrong value is read)

This may or may not be a problem depending on what your application is going to do as a result. If the wrong value will be flashed on the GUI for 1 second, then its probably not a big deal, and the performance penalty of the lock can be ignored. If your program is going to take action based on that value, then you probably need to lock it down.

A simple answer is that you need to schedule the property updates in UI Thread through UI Thread's Dispatcher. This will put updates operation in a queue which will not crash the application.

private void handler(object sender, EventArgs e)
{
    Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate { updates(); });
}

private void updates() { /* real updates go here */ }

Some thing more elaborate that it is actually so simple... When instantiating your viewmodel in your view just pass the dispatcher down in the ctor.

    ServerOperationViewmodel ViewModel;        
    public pgeServerOperations()
    {
        InitializeComponent();
        ViewModel = new ServerOperationViewmodel(Dispatcher);
    }

Then in your View model:

Dispatcher UIDispatcher;
public ServerOperationViewmodel(Dispatcher uiDisp)
{
    UIDispatcher = uiDisp;
}

And use it like a normal UI dispatcher.

UIDispatcher.Invoke(() =>
{
  .......
});

I will admit that I am still fairly new to MVVM, but I don not think this breaks the MVVM motto.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM