1

I've got small WPF / MVVM example project with two visual elements (a ComboBox and a simple TextBlock). Both elements are bound to a property of my ViewModel:

Properties MainViewModel.cs

public const string WelcomeTitlePropertyName = "WelcomeTitle";
private string _welcomeTitle = string.Empty;

public string WelcomeTitle
{
    get{ return _welcomeTitle;}
    set
    {
        _welcomeTitle = value;
        RaisePropertyChanged(WelcomeTitlePropertyName);
    }
}

public const string PositionsPropertyName = "Positions";
private ObservableCollection<int> _positions = new ObservableCollection<int>();

public ObservableCollection<int> Positions
{
    get{ return _positions; }

    set
    {
        _positions = value;
        RaisePropertyChanged(PositionsPropertyName);
    }
}

Bindings MainWindow.xaml

<StackPanel>
    <TextBlock Text="{Binding WelcomeTitle}"/>
    <ComboBox ItemsSource="{Binding Positions}" />
</StackPanel>

Now I change both properties from a non UI thread like this (which is not allowed, as far as I know it):

    System.Threading.ThreadPool.QueueUserWorkItem(delegate
    {
        int i = 0;
        while(true)
        {
            Positions.Add(i); // Solution 1: this throws NotSupportedException
            WelcomeTitle = i.ToString(); // Solution 2: this works

            i++;
        }
    }, null);

Question:

Why does solution 1 throw a NotSupportedExpection (not allowed to change collection from non dispatcher thread) while solution 2 works as desired?

nabulke
  • 11,025
  • 13
  • 65
  • 114
  • I don't see a solution 1 and 2 ... (edit - doh! I'm an ejit) – Mashton Jan 14 '14 at 13:35
  • Look in the last example code comments – nabulke Jan 14 '14 at 13:36
  • Why does #1 not work/how to make it work? See http://stackoverflow.com/q/2091988/50079. Why does #2 work? Because there is no reason it shouldn't (#1 is a special case, otherwise that would have worked too). Also, "dependency property" is not the right term. That's a plain .NET property. – Jon Jan 14 '14 at 13:37
  • In general, what happens is that when a "changed" event is raised on a non-UI thread, you have to dispatch any UI-related changes to the UI thread. As long as the machinery does that automatically, you are golden. If it doesn't, the burden is on you to make sure the change itself is made on the UI thread. – Jon Jan 14 '14 at 13:40
  • See the `Dispatcher.BeginInvoke()` method, which can be used to queue up a delegate to be executed on the UI thread. http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.begininvoke(v=vs.110).aspx – Steve Jan 14 '14 at 13:50
  • @Jon, you seem to have hit the nail on the head with your comment... is there any chance that you could copy those comments into an answer, so that this question can be marked as correct? – Sheridan Jan 14 '14 at 13:58
  • 1
    @Sheridan: I expanded the comments into a proper answer. – Jon Jan 14 '14 at 14:14

2 Answers2

2

Now I change both properties from a non UI thread like this (which is not allowed, as far as I know it)

In general, changing property values is perfectly fine no matter what thread you are on. Problems and restrictions may come up when changing a property has an "interesting" side effect.

In this case both of the properties being changed produce interesting side effects, and the difference in observed behavior is due to these side effects being handled (from framework code, which you do not get to see directly) in different ways.

Why does solution 1 throw a NotSupportedExpection (not allowed to change collection from non dispatcher thread) while solution 2 works as desired?

When a binding source's properties are changed the WPF binding system responds by making the corresponding updates to the UI; however, when the changes are made from a background thread then the binding system's event handler will also run in the background thread and will not be able to update the UI directly. This is true for both the cases in question here.

The difference is that for "simple" property value changes the binding system automatically detects that it's not responding to the change on the UI thread and dispatches the UI changes to the correct thread using Dispatcher.Invoke, but when the observable collection is modified this dispatch does not happen automatically. The result is that the code that updates the UI runs on the background thread and an exception is thrown.

The solution

There are two things either one of which can solve this problem:

  1. Make the property change in the UI thread directly

    If the change is made on the UI thread then any PropertyChanged handlers will also run on the UI thread, so they will be free to make any UI changes they want. This solution can be enforced in your own code and will never result in a problem, but if no UI changes are required the extra work of dispatching to the UI thread will have been done for no benefit.

  2. Make sure the PropertyChanged handler dispatches any UI-related changes to the UI thread

    This solution has the benefit that it only dispatches work on demand, but also the drawback that the event handler (which might not be your own code) must be explicitly programmed to make the dispatch. .NET already does this for plain properties, but not for ObservableCollection. See How do I update an ObservableCollection via a worker thread? for more information.

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

The simple Property binding is automatically dispatched to the GUI thread by WPF and can be changed from a non-UI thread. However, this is NOT true for collection changes (ObservableCollection<> BindingList<>). Those changes must happen the UI thread the control was created on. If I remember correctly, this was not true (solution 2 did not work also) in the early years of WPF and .NET.

Rudolfking
  • 81
  • 3