dotnet/wpf · error · ArgumentException
SR.SourcesMustBeInSameThread
Error message
SR.SourcesMustBeInSameThread
What it means
Thrown by UpdateExpressionSource (source-update path invoked from expression handling) when a new expression source's Dispatcher differs from the target DependencyObject's Dispatcher. Expression sources must live on the same thread as the dependant object, unless the expression declares SupportsUnboundSources and the source has no dispatcher.
Solutions
- Create all source DependencyObjects on the same thread as the target
- If sources are genuinely thread-free, use an expression type whose SupportsUnboundSources is true and ensure source Dispatcher is null
- Marshal source updates to the target's Dispatcher before calling source-change APIs
- Restructure so the source is a plain (non-DependencyObject) value exposed through a property
Example fix
// before
var src = Task.Run(() => new DispatcherObject()).Result; // wrong thread
// after
var src = new DispatcherObject(); // created on the UI thread
expr.ChangeSources(target, dp, new DependencySource[] { new DependencySource(src, prop) }); Defensive patterns
Strategy: validation
Validate before calling
foreach (var s in newSources) {
if (s.DependencyObject.Dispatcher != target.Dispatcher && !(expr.SupportsUnboundSources && s.DependencyObject.Dispatcher == null))
throw new InvalidOperationException("Cross-thread expression source");
}
expr.ChangeSources(target, dp, newSources); Type guard
static bool SameThread(DependencyObject a, DependencyObject b) => a.Dispatcher == b.Dispatcher;
Try / catch
try { expr.ChangeSources(target, dp, sources); }
catch (ArgumentException) when (sources.Any(s => s.DependencyObject.Dispatcher != target.Dispatcher)) { target.Dispatcher.Invoke(() => expr.ChangeSources(target, dp, sources)); } Prevention
- Create all source DependencyObjects on the target's thread
- Marshal cross-thread updates with Dispatcher.Invoke/BeginInvoke
- Only rely on SupportsUnboundSources for genuinely thread-free sources
When it happens
Trigger: An Expression (custom Binding-like object) declares DependencySources whose DependencyObjects were created on (or marshaled to) a different thread than the target; calling ChangeSources with cross-thread sources.
Common situations: Background-thread-created objects used as binding sources, dispatcher affinity mistakes when moving elements between threads, custom markup extensions returning sources from another thread.
Related errors
- Processing is disabled while the Dispatcher is in this…
- SR.CollectionView_MissingSynchronizationCallback
- SR.ContextMenuInDifferentDispatcher
- SR.CurrentDispatcherNotFound
- SR.DispatcherHasShutdown
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/784bfc6572620906.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/DependencyObject.cs:2437
// Set the updated struct back into the source's _localStore.
DependentListMapField.SetValue(source.DependencyObject, dependentListMap);
}
}
}
}
internal static void ValidateSources(DependencyObject d, DependencySource[] newSources, Expression expr)
{
// Make sure all Sources are owned by the same thread.
if (newSources != null)
{
Dispatcher dispatcher = d.Dispatcher;
for (int i = 0; i < newSources.Length; i++)
{
Dispatcher sourceDispatcher = newSources[i].DependencyObject.Dispatcher;
if (sourceDispatcher != dispatcher && !(expr.SupportsUnboundSources && sourceDispatcher == null))
{
throw new ArgumentException(SR.SourcesMustBeInSameThread);
}
}
}
}
/// <summary>
/// Register the two callbacks that are used to implement the "alternative
/// Expression storage" feature, and return the two methods used to access
/// the feature.
/// </summary>
/// <remarks>
/// This method should only be called (once) from the Framework. It should
/// not be called directly by users.
/// </remarks>
internal static void RegisterForAlternativeExpressionStorage(
AlternativeExpressionStorageCallback getExpressionCore,
out AlternativeExpressionStorageCallback getExpression)
{View on GitHub (pinned to 81131a70a4)