3

Ok, following on from yesterday I've added a new layer of complexity. We still have a theoretical Model class, ViewModel and View. This time my Model has a Threading.Timer (Chosen specifically to get timer callbacks on the "wrong" thread.

The Model has an ObservableCollection. The Timer callback adds items to the collection.

The ViewModel simply passes the collection to the View which contains a listbox bound to the collection.

This doesn't work.

The model also exposes a string which is updated in the same timer callback.

This too is exposed via the viewmodel and bound to a TextBox.

This does work.

I've seen hints in my googling that updating collections doesn't make INotifyCollectionChanged work as expected. I get a total implosion, not even an exception, just immediate termination of the application.

So there are two questions:

One relates to our discussion yesterday. I'm using INotifyPropertyChanged and ObservableCollections in my Model because they are the thing upon which the view does work. It still makes sense to me to use these mechanisms to notify my viewmodel, or what ever that the underlying model has changed. So how do I deal with updates occuring on a different thread?

Second, what is happening that makes INotifyPropertyChanged work with the binding? I'm binding a string property to a DependencyProperty called Text, so is it the DependencyProperty system that marshals my change back to the UI thread? Edit: And can I rely on it, i.e. does it do this because they expect me to talk to it cross-thread, or is it just a catch all that I shouldn't rely on?

The ListBox is bound through ItemsSource="{Binding ObsCollection}". When this crashes the application. Actually, at first I started the timer when the Model was created which happened when the Window's DataContext was set, so it would actually bomb Visual Studio...

Thanks

Ian
  • 4,885
  • 4
  • 43
  • 65
  • Does this answer your question? [How do I update an ObservableCollection via a worker thread?](https://stackoverflow.com/questions/2091988/how-do-i-update-an-observablecollection-via-a-worker-thread) – Peter Duniho Mar 09 '20 at 07:43

2 Answers2

2

WPF controls have thread-affinity, what this means is that their properties can only be modified from the UI thread. Therefore, if you update a property value from a Timer (other than a DispatcherTimer), you will have to marshal this update onto the UI thread. This is perfomed via the dispatcher:

Application.Current.Dispatcher.BeginInvoke(
  DispatcherPriority.Normal,
  new Action(() => // update your control here));

The databinding framework does not ensure that updates are marshalled onto the UI thread, therefore, if you update your model from a different thread, this will cause issues. therefore you need to use the same pattern as above. In other words, if you are adding objections to your observable collection, this add must be performed via the Dispatcher.

ColinE
  • 68,894
  • 15
  • 164
  • 232
  • 1
    That's cool, but what is the "correct" process for dealing with this from an MVVM perspective? So a thread on my Model is updating a Collection. Should my Model now know about Dispatchers? That seems wrong. Which suggests making my ViewModel heavier to cope with the correct seperation between Model and View. Does that make sense? – Ian Jan 05 '11 at 12:54
  • 1
    There is no 'correct' approach. However, my personal preference is to create simple interface that the ViewModel depends on, IMarshalledInvoker, which has a single method for invoking some action on some other thread. When I couple the ViewModel with the View, I create an IMarshalledInvoker that uses the Dispatcher under the covers. With this pattern I can still unit test, can still have designer support, i.e. it is good MVVM ;-) – ColinE Jan 05 '11 at 13:12
  • Ah ok, things are coming together now. Comically there are disparate bits of knowledge that relate to each other in ways I hadn't considered. First biggie, we know that making a thread safe collection is tricky. I'd forgotten that and hadn't made the association in my head... Essentially I am only adding on one thread. Funny how you forget why you do things.So I think the nub of the answer is to funnel changes from multiple threads into a single thread. So there's a single channel between the view and the viewmodel?? – Ian Jan 05 '11 at 13:14
  • Thanks Colin, I was typing my last comment when you added yours :) Ricky Gervais's cat is called Colin, that's not you is it?? ;) – Ian Jan 05 '11 at 13:15
  • Meow. No. I am a different cat. Please mark as answer if this has helped. – ColinE Jan 05 '11 at 13:16
  • Sure, I think I've got enough to ask a more focused question now :) – Ian Jan 05 '11 at 13:31
  • 1
    Not quite. The data binding framework does in fact ensure that if it gets notified of a property change, it will set the actual control's property on the UI thread (there's even a DispatcherPriority.DataBinding priority). This is why the string text binding works for OP. The problem is that the databinding framework does not have this for INotifyCollectionChanged bindings. This is why we have to marshal any modification calls to ObservableCollection to the UI thread. – Szymon Rozga Jan 05 '11 at 14:41
2

This problem is quite prevalent in WPF. I think the best option is to have your own ObservableCollection<> subclass that takes care of dispatching event notifications to the UI thread automatically.

And since the wheel has already been invented, I 'm going to simply refer you to the answer to this question: ObservableCollection and threading.

Community
  • 1
  • 1
Jon
  • 428,835
  • 81
  • 738
  • 806