stride3d/stride · error · InvalidOperationException

The current thread was expected to be the dispatcher thread.

Error message

The current thread was expected to be the dispatcher thread.

What it means

DispatcherService.EnsureAccess verifies thread affinity for the dispatcher it wraps. With the default inDispatcherThread=true, it throws InvalidOperationException when called from any thread other than the dispatcher thread, protecting WPF objects that can only be touched by their owning thread.

Solutions

  1. Wrap the affected work in dispatcher.Invoke or dispatcher.InvokeAsync so it runs on the dispatcher thread
  2. If the check intent was the opposite, pass EnsureAccess(false) to require a non-dispatcher thread
  3. Remove or re-scope the EnsureAccess call if the code intentionally runs off-thread and marshals later

Example fix

// before
Task.Run(() => { dispatcherService.EnsureAccess(); UpdateUi(); });
// after
dispatcherService.InvokeOrBeginInvoke(() => UpdateUi()); // work marshaled onto dispatcher thread
Defensive patterns

Strategy: try-catch

Validate before calling

if (dispatcherService.CheckAccess()) DoWork(); else dispatcherService.Invoke(() => DoWork());

Type guard

bool OnDispatcher(DispatcherService s) => s.CheckAccess();

Try / catch

try { s.EnsureAccess(); } catch (InvalidOperationException ex) when (ex.Message.Contains("expected to be the dispatcher thread")) { dispatcher.Invoke(Work); }

Prevention

When it happens

Trigger: Calling EnsureAccess() (or EnsureAccess(true)) from a background/worker thread; performing UI updates inside Task.Run or a background thread callback and asserting dispatcher affinity.

Common situations: Updating observable collections or view models from async work or ThreadPool threads; forgetting to marshal work with dispatcher.Invoke/InvokeAsync; unit tests running assertions on non-UI threads.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/288a1304cdaa09b8. Report an issue: GitHub.

Appendix: source

Thrown at sources/presentation/Stride.Core.Presentation.Wpf/View/DispatcherService.cs:118

        [NotNull]
        public static Task<TResult> InvokeTask<TResult>([NotNull] Dispatcher dispatcher, Func<Task<TResult>> task, CancellationToken token = default)
        {
            var tcs = new TaskCompletionSource<TResult>();
            dispatcher.InvokeAsync(async () => tcs.SetResult(await task()), DispatcherPriority.Normal, token);
            return tcs.Task;
        }

        /// <inheritdoc/>
        public bool CheckAccess()
        {
            return Thread.CurrentThread == dispatcher.Thread;
        }

        /// <inheritdoc/>
        public void EnsureAccess(bool inDispatcherThread = true)
        {
            if (inDispatcherThread && Thread.CurrentThread != dispatcher.Thread)
                throw new InvalidOperationException("The current thread was expected to be the dispatcher thread.");
            if (!inDispatcherThread && Thread.CurrentThread == dispatcher.Thread)
                throw new InvalidOperationException("The current thread was expected to be different from the dispatcher thread.");
        }
    }
}

View on GitHub (pinned to 96fad776d2)