0

I cannot understand if it is mandatory to use Dispatcher to notify UI thread that a bound property has been updated by a non-UI thread.
Starting from a basic example, why is the following code not throwing the famous (I remember struggling on this in past coding experiences) The calling thread cannot access this object because a different thread owns it exception?

private int _myBoundProperty;
public int MyBoundProperty { get { return _myBoundProperty; } set { _myBoundProperty = value; OnPropertyChanged(); } }
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void Test()
{
    new System.Threading.Thread(() =>
    {
        try
        {
            MyBoundProperty += DateTime.Now.Second;
        }
        catch(Exception ex)
        {
        }
    }).Start();
}

<TextBlock Text="{Binding MyBoundProperty"/>

I saw some posts on this topic, but they seem to me quite confusing:

  • someone stating Dispatcher is required for collections but not for single variables: so why this other example still not throwing exception?
private ObservableCollection<int> _myBoundPropertyCollection;
public ObservableCollection<int> MyBoundPropertyCollection { get { return _myBoundPropertyCollection; } set { _myBoundPropertyCollection = value; OnPropertyChanged(); } }
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void TestCollection()
{
    new System.Threading.Thread(() =>
    {
        try
        {
            //MyBoundPropertyCollection = new ObservableCollection() { 0 }; //even so not throwing
            MyBoundPropertyCollection[0] += DateTime.Now.Second;
        }
        catch(Exception ex)
        {
        }
    }).Start();
}

<TextBlock Text="{Binding MyBoundPropertyCollection[0]"/>
  • someone stating it was something required by previous .NET versions: so now can we removed those Dispatcher calls (I am using .NET Framework 4.7)?
  • someone stating it is a good practice to use Dispatcher even if your code does not throw exception: seems like an act of faith...
Giacomo Pirinoli
  • 548
  • 6
  • 17
  • 2
    Yes you need to use Dispatcher to update WPF controls. When in doubt, read the [documentation](https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/threading-model). – Zer0 Mar 13 '20 at 20:01
  • @Zer0 so it's just a random and particular scenario the code provided above not throwing exception (on repeated test runs)? hard to believe..... – Giacomo Pirinoli Mar 13 '20 at 20:03
  • 3
    That's not even remotely uncommon. This threading question has persisted since WinForms. I would recommend you become knowledgeable as to why the WPF GUI thread is required to be marked with `[STAThread]`. Just because a control doesn't throw an exception when updated from a background thread does *not* make it a good idea. It has always been unsafe. – Zer0 Mar 13 '20 at 20:06

1 Answers1

2

Your first example isn't modifying any UI thread related DispatcherObject from a forbidden thread. The binding engine automatically marshals the INotifyPropertyChanged.PropertChanged event or the invocation of it's registered event handlers to the UI thread. This means the UI's Binding.Target, which is a DispatcherObject, is always correctly updated on the UI thread.

// Safe, because PropertyChanged will always be raised on the UI thread
MyBoundProperty = DateTime.Now; 

This doesn't apply for INotifyCollectionChanged.CollectionChanged. A collection changed from a background thread must marshal the modification of the collection manually either by invoking the Dispatcher that is registered for the thread or by capturing the SynchronizationContext.Current of the owning or calling thread to be able to post the critical operation back to the proper context (thread).

// Assuming that this is the proper thread of the DispatcherObject, 
// that binds to MyBoundPropertyCollection
SynchronizationContext capturedSynchronizationContext = SynchronizationContext.Current;

Task.Run(() =>
  {
    // Will throw a cross-thread exception
    MyBoundPropertyCollection.Add(item); 

    // Safe
    Dispatcher.Invoke(() => MyBoundPropertyCollection.Add(item)); 

    // Safe
    capturedSynchronizationContext.Post(state => MyBoundPropertyCollection.Add(item), null); 
  });

Since your second example is equal to the first example, it won't throw for the very same reason. You are not modifying the collection e.g. Add/Insert/Move/Remove, but an item contained in this collection:

Task.Run(() => MyBoundPropertyCollection[0] = DateTime.Now);

is equal to

Task.Run(() =>
  {
    var myBoundProperty = MyBoundPropertyCollection[0];

    // Safe, because PropertyChanged will always be raised on the UI thread
    myBoundProperty = DateTime.Now; 
  });

Every object that derives from DispatcherObjecte.g. TextBox is associated with the thread it was created on (thread affinity) by mapping it to the thread's Dispatcher. Only this thread is allowed to modify the DispatcherObject. Other threads have to use either the associated Dispatcher or the SynchronizationContext of the DispatcherObject owner thread.

If you want to pass a DispatcherObject from one thread to another e.g., pass a Brush to the UI thread, then this is only possible when the DispatcherObject derives from Freezable. You'd call Freezable.Freeze, which lifts the thread affinity i.e Dispatcher affinity and allows to pass the instance to some other thread.

BionicCode
  • 1
  • 4
  • 28
  • 44
  • Ok, your explanation is clear to me and I will go in deeper details checking some additional documentation on this topic; I also get the uselessness of the second example I wrote, actually it's equal to the first one. The only disappointing aspect is that ```MyBoundPropertyCollection.Add(someint);``` and ```MyBoundPropertyCollection.Insert(0, someint);``` are still not throwing exceptions (on repeated runs).....but I'll trust you, and avoid doing this for theoretical correctness. – Giacomo Pirinoli Mar 14 '20 at 12:16
  • 1
    The point is, it's all about the `DispatcherObject`. If the instance is not a `DispatcherObject`, it can be modified from any thread. To get a cross-thread exception a `DispatcherObject` must bind to it. If you bind a `ListBox` to it, an exception will be thrown, because `ListBox` derives from `DispatcherObject`. – BionicCode Mar 14 '20 at 15:30
  • 1
    Now when `INotifyCollectionChanged.CollectionChanged` is raised from a different thread than the thread the `DispatcherObject` was created on, the exception is thrown, because the event handlers are executed on the thread the event is raised on. The event handler of e.g. `ItemsControl` will update (modify) the control (`DispatcherObject`) to update itself. If this happens on the wrong threadthen the cross-threading exception is thrown. – BionicCode Mar 14 '20 at 15:35
  • almost there. ```MyBoundPropertyCollection``` is an ```ObservableCollection``` hence not a ```DispatcherObject```. If bound to ```TextBlock.Text``` it **doesn't** throw exception (if thread is updating single element as well as modifying the collection), ```TextBlock``` is a ```DispatcherObject```, ```Text``` is not a ```DispatcherObject```. If bound to ```ListBox.ItemsSource``` it **does** throw exception (if thread is updating single element as well if modifying the collection), ```ListBox``` is a ```DispactherObject```, ```ItemsSource``` is not a ```DispatcherObject```. This due to... – Giacomo Pirinoli Mar 14 '20 at 18:36
  • Your observation is right and absolutely as expected. The rule will always stand, no matter what you may observe: you can't change a `DispatcherObject` from a different thread, than the thread it was created on. This is how the frameworks dependency system is implemented. Or to be more precise, this is how the frameworks threading model is implemented in order to allow backwards compatibility with the old User32 model. – BionicCode Mar 14 '20 at 21:47
  • If you are interested, I recommend this read: [Microsoft Docs: WPF Architecture - System.Threading.DispatcherObject](https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/wpf-architecture?redirectedfrom=MSDN#systemthreadingdispatcherobject). Back to your observed behavior. To understand this behavior, you have to check implementation details of WPF controls. – BionicCode Mar 14 '20 at 21:48
  • First, `ItemsSource` or `Text` is a _property_ (`DependencyProperty`) and not an _object_. Although the _value_ of a property is an object too. It's about object instances. When we say we modify an instance, then we are talking about changing its state by changing the values of public properties or even private properties or fields by invoking a public method. This means, whenever a _member_ of a `DispatcherObject` is modified from a different thread than the thread it was created on, an exception is thrown. – BionicCode Mar 14 '20 at 21:49
  • 1
    Now the implementation details. The `ItemsControl.ItemsSource` property is of type `IEnumerable` to offer maximum flexibility. That's why you can bind e.g. a `List` or a `DataTable` to it. Now, when you assign a value to the `ItemsControl.ItemsSource` property, the `ItemsControl` (or the `ItemContainerGenerator` to be more precise) checks if the assigned `IEnumerable` implements `INotifyCollectionChanged`. If so, the control subscribes to the `CollectionChanged` event in order to get notified about collection changes, so that it can update itself to render the changes. – BionicCode Mar 14 '20 at 21:49
  • 1
    But the `TextBlock` is not implemented like this. The `TextBlock` is not intended to show a collection of items. It expects a `string` value to be assigned to the `Text` property. So assigning a value to this property won't result in a check if it is an `INotifyCollectionChanged` and therefore the `TextBlock` will never subscribe to the `CollectionChanged` event, which is why there is no event handler that modifies `TextBlock`and is probably executed on the wrong thread. The actual property _value_ itself has never changed, as it is still the same instance of e.g. `MyBoundPropertyCollection`. – BionicCode Mar 14 '20 at 22:17
  • 1
    The `TextBlock` will only try to convert a non `string` to a `string` by invoking `object.ToString`. That's why you see the collection type as the value of the `Text` property. If you want to know how .NET types are implemented look it up on [Microsoft .NET Reference Source](https://referencesource.microsoft.com/). – BionicCode Mar 14 '20 at 22:17
  • 1
    _"A collection changed from a background thread must marshal the modification of the collection manually"_ -- this is no longer true (and has not been for years now). See e.g. https://stackoverflow.com/a/14602121. It does require more care than basic property binding, since you still have to decide on and cooperate with some kind of synchronization mechanism. But once done, the actual marshaling is transparent. – Peter Duniho Mar 14 '20 at 22:22
  • @PeterDuniho Very good point. I should add it to the answer for completeness. But my statement is still valid as it relates to the default behavior. `BindingOperations.EnableCollectionSynchronization` also needs to be invoked for every collection individually. I would call this very _"manually"_. Marshaling of `PropertyChanged` on the other hand is handled by the framework. – BionicCode Mar 14 '20 at 22:52