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

  1. Freeze the child Freezable before using it cross-thread (frozen objects are thread-safe and context-free)
  2. Create the child object on the same Dispatcher/thread as the owner
  3. Pass a Clone/CloneCurrentValue made on the owner's thread instead of the original instance
  4. 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

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


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)