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

  1. Create all source DependencyObjects on the same thread as the target
  2. If sources are genuinely thread-free, use an expression type whose SupportsUnboundSources is true and ensure source Dispatcher is null
  3. Marshal source updates to the target's Dispatcher before calling source-change APIs
  4. 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

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


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)