dotnet/wpf · error · InvalidOperationException
SR.Freezable_AttemptToUseInnerValueWithDifferentThread
Error message
SR.Freezable_AttemptToUseInnerValueWithDifferentThread
What it means
Freezable.EnsureConsistentDispatchers enforces that an owner Freezable and its child Freezable either live on the same Dispatcher (thread) or that one of them is context-free (Dispatcher == null). When both have non-null but different Dispatchers, attaching the child throws InvalidOperationException(SR.Freezable_AttemptToUseInnerValueWithDifferentThread), because the dependency graph would span threads that cannot invalidate each other.
Solutions
- Freeze the child Freezable before using it cross-thread (frozen objects are thread-safe and context-free)
- Create the child object on the same Dispatcher/thread as the owner
- Pass a Clone/CloneCurrentValue made on the owner's thread instead of the original instance
- Check Dispatcher equality before assignment and marshal via Dispatcher.Invoke if needed
Example fix
// before uiElementBrush.GradientStops.Add(stopBuiltOnWorkerThread); // cross-thread // after stopBuiltOnWorkerThread.Freeze(); uiElementBrush.GradientStops.Add(stopBuiltOnWorkerThread); // frozen: OK
Defensive patterns
Strategy: validation
Validate before calling
if (owner.Dispatcher != null && child.Dispatcher != null && owner.Dispatcher != child.Dispatcher && !child.IsFrozen)
child = (Freezable)child.GetAsFrozen(); // or marshal to owner's thread Type guard
static bool SameThreadOrFreezable(System.Windows.Freezable owner, System.Windows.Freezable child) => child.IsFrozen || owner.Dispatcher == child.Dispatcher || child.Dispatcher == null;
Try / catch
try { owner.SetValue(prop, child); }
catch (InvalidOperationException ex) { log.Error("Cross-thread Freezable child; freeze or marshal first", ex); } Prevention
- Freeze any Freezable produced on background threads before handing it to the UI thread
- Build UI-bound Freezables on the owner's Dispatcher
- Compare Dispatchers before composing object graphs across threads
When it happens
Trigger: Assigning a frozen/created-on-another-thread Freezable (e.g. a Brush built on a background thread) as a property value or sub-object of a Freezable owned by the UI thread, e.g. via OnFreezablePropertyChanged during property setting or cloning.
Common situations: Building visuals/brushes on worker threads and handing them to UI-thread objects; mixing objects across threads when pre-rendering for performance; forgetting to freeze or to create the child on the consuming 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/22b9ed353dc62402.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Freezable.cs:1014
SetValue(dp, sourceValue);
}
}
}
// Throws if owner/child are not context free and on different dispatchers.
private static void EnsureConsistentDispatchers(DependencyObject owner, DependencyObject child)
{
Debug.Assert(owner != null && child != null,
"Caller should guard against passing null owner/child.");
// It is illegal to set a DependencyObject from one Dispatcher into a owner
// being serviced by a different Dispatcher (i.e., they need to be on
// the same thread or be context free (Dispatcher == null))
if (owner.Dispatcher != null &&
child.Dispatcher != null &&
owner.Dispatcher != child.Dispatcher)
{
throw new InvalidOperationException(
SR.Freezable_AttemptToUseInnerValueWithDifferentThread);
}
}
// These methods provide an abstraction for managing Freezable context
// information - the context information being DO/DP pairs that the Freezable maps to.
//
// The methods will attempt to use as little memory as possible to store this information.
// When there is only one context it will store the information directly, otherwise it will
// place it within a list. When using a list, these methods place the DO/DP pairs so
// that DOs are grouped together. This is done so that when walking the graph, it
// is easier to track which DO's change handlers have already been gathered.
//
/// <summary>
/// Removes the context information for a Freezable.
/// <param name="context">The DependencyObject to remove that references this Freezable.</param>
/// <param name="property">The property of the DependencyObject this object maps to or null if none.</param>View on GitHub (pinned to 81131a70a4)