1

I'm trying to use Task.Factory.StartNew() to run a background operation. Part of the background operation updates an object that's held in an ObservableCollection. I'm using a custom class derived from ObservableCollection to fire OnCollectionChanged() when a property on one of the objects in the collection is changed (see https://stackoverflow.com/a/5256827/62072). If there is a CollectionView bound to the ObservableCollection then I get an exception:

System.NotSupportedException: This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

I'm trying to avoid this exception so I've added some code to only fire OnCollectionChanged() if running on the UI thread. But somehow I still get the exception..

Here's my ItemPropertyChanged() method:

void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    var a = new NotifyCollectionChangedEventArgs(
        NotifyCollectionChangedAction.Reset);

    if (Thread.CurrentThread.ManagedThreadId
         == Dispatcher.CurrentDispatcher.Thread.ManagedThreadId)
    {
        OnCollectionChanged(a);
    }
}

And here's the full exception:

System.AggregateException was unhandled
  Message=A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread.
  Source=mscorlib
  StackTrace:
       at System.Threading.Tasks.TaskExceptionHolder.Finalize()
       Message=This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
      InnerException: System.NotSupportedException
           Source=PresentationFramework
           StackTrace:
                at System.Windows.Data.CollectionView.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)
                at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
                at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
                at SourceLog.Model.TrulyObservableCollection`1.ItemPropertyChanged(Object sender, PropertyChangedEventArgs e) in C:\github.com\tomhunter-gh\SourceLog\SourceLog.Model\TrulyObservableCollection.cs:line 41
                at System.ComponentModel.PropertyChangedEventHandler.Invoke(Object sender, PropertyChangedEventArgs e)
                at SourceLog.Model.LogEntry.OnPropertyChanged(String property) in C:\github.com\tomhunter-gh\SourceLog\SourceLog.Model\LogEntry.cs:line 44
                at SourceLog.Model.LogEntry.set_Read(Boolean value) in C:\github.com\tomhunter-gh\SourceLog\SourceLog.Model\LogEntry.cs:line 28
                at SourceLog.Model.LogEntry.<MarkAsReadAndSave>b__0() in C:\github.com\tomhunter-gh\SourceLog\SourceLog.Model\LogEntry.cs:line 53
                at System.Threading.Tasks.Task.InnerInvoke()
                at System.Threading.Tasks.Task.Execute()
           InnerException: 

How come the exception complains that I'm not on the Dispatcher thread when I've explicitly checked that I am?

Community
  • 1
  • 1
Tom Hunter
  • 5,714
  • 10
  • 51
  • 76
  • This is an alternative ObservableCollection that allows notifications and changes to be made from a thread, and it will handle all the synchronization back to the UI thread. http://www.deanchalk.me.uk/post/Thread-Safe-Dispatcher-Safe-Observable-Collection-for-WPF.aspx ... make sure you create it in the UI thread...as it captures the current dispatcher (in the constructor)...then you can pass the reference to your background thread to use. – Colin Smith Aug 24 '12 at 14:05

2 Answers2

6

Dispatcher.CurrentDispatcher has nothing to do with the UI-thread, as the name of the property suggests it gives you the dispatcher of the current thread. So you created a check which will always return true. Use Application.Current.Dispatcher, also instead of ignoring the change you can invoke it on said dispatcher.

H.B.
  • 166,899
  • 29
  • 327
  • 400
  • So how many `Dispatchers` out there ?? He doesn't check it correctly - he should [Dispatcher.CheckAccess()][1] but `System.Windows.Deployment.Dispatcher.BeginInvoke(()=>{});` will do the job. Isn't it ? [1]: http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.checkaccess.aspx – Alex F Aug 27 '12 at 06:22
  • @Jasper: The number of dispatchers is less than or equal to the number threads, a thread may or may not own a dispatcher. If `CurrentDispatcher` is invoked a new dispatcher is created for the thread if it does not already have one. As i see it `CheckAccess` is pretty unnecessary, if you always dispatch to the UI dispatcher the only difference is that you can directly execute the code rather then pushing it in the processing queue, should not be that big a deal. `BeginInvoke` may work, but it is asynchronous, so you have to watch out for race-conditions. – H.B. Aug 27 '12 at 06:32
  • B.T.W: Did you notice root exception is coming from `Task` ? I was about to suggest SynchronizationContext [http://msdn.microsoft.com/en-us/library/wx31754f] class but guess I was wrong. I guess he/she raising `OnCollectionChanged` inside a `Task` without synchronization context and not even `Wait()` for it... – Alex F Aug 27 '12 at 06:44
2

You need to switch context back into the UI thread when you access the observable collection. Try

Action del = () => {YourCodeHere()};
Dispatcher.Invoke(del);