dotnet/wpf · critical · NotSupportedException
SR.MultiThreadedCollectionChangeNotSupported
Error message
SR.MultiThreadedCollectionChangeNotSupported
What it means
CollectionView.OnCollectionChanged throws NotSupportedException when the underlying collection raised CollectionChanged from a different thread while the view does not allow cross-thread changes. WPF collection views are bound to the thread that created them (Dispatcher affinity), so change notifications must arrive on that thread.
Solutions
- Marshal mutations to the UI thread via Dispatcher.Invoke/BeginInvoke (or SynchronizationContext.Post)
- Use BindingOperations.EnableCollectionSynchronization with a lock so the binding engine can handle cross-thread updates
- Update the collection only on the thread that created the view
- Build the data on the background thread and assign the finished collection to ItemsSource on the UI thread
Example fix
// before Task.Run(() => items.Add(newItem)); // NotSupportedException in view // after Application.Current.Dispatcher.Invoke(() => items.Add(newItem)); // or once at startup: BindingOperations.EnableCollectionSynchronization(items, _lock);
Defensive patterns
Strategy: try-catch
Validate before calling
if (!collectionView.CheckAccess())
Dispatcher.CurrentDispatcher/* or view.Dispatcher */.Invoke(() => source.Add(item));
else
source.Add(item); Type guard
bool canChangeHere = view is CollectionView cv && cv.CheckAccess();
Try / catch
try { source.Add(item); }
catch (NotSupportedException) { view.Dispatcher.Invoke(() => source.Add(item)); } Prevention
- Only mutate bound collections on the UI thread
- Use BindingOperations.EnableCollectionSynchronization for genuinely multi-threaded collections
- Marshal background results via Dispatcher.BeginInvoke before touching the collection
When it happens
Trigger: Mutating an ObservableCollection from a background thread (Task, ThreadPool, socket callback) while a CollectionView bound to it lives on the UI thread; the view then calls OnCollectionChanged off-dispatcher and CheckAccess() fails.
Common situations: Loading data async and writing results directly into an ObservableCollection consumed by an ItemsControl; background services pushing updates into a bound collection; worker threads updating shared collections.
Related errors
- SR.CollectionView_MissingSynchronizationCallback
- Processing is disabled while the Dispatcher is in this…
- SR.ContextMenuInDifferentDispatcher
- SR.CurrentDispatcherNotFound
- SR.DispatcherHasShutdown
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/fe47a18dfa56bb25.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/CollectionView.cs:1165
///
/// Calls ProcessCollectionChanged() or
/// posts the change to the Dispatcher to process on the correct thread.
///</summary>
/// <remarks>
/// User should override <see cref="ProcessCollectionChanged"/>
/// </remarks>
/// <param name="sender">
/// </param>
/// <param name="args">
/// </param>
protected void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
if (CheckFlag(CollectionViewFlags.ShouldProcessCollectionChanged))
{
if (!AllowsCrossThreadChanges)
{
if (!CheckAccess())
throw new NotSupportedException(SR.MultiThreadedCollectionChangeNotSupported);
ProcessCollectionChanged(args);
}
else
{
PostChange(args);
}
}
}
/// <summary>
/// This method is called when the value of AllowsCrossThreadChanges
/// is changed. It gives a derived class an opportunity to
/// initialize its support for cross-thread changes (or to retire
/// that support).
/// </summary>
/// <notes>
/// This method will only be called if the application chooses to
/// change the synchronization information for a collection afterView on GitHub (pinned to 81131a70a4)