stride3d/stride · error · InvalidOperationException
This method must be invoked from the dispatcher thread
Error message
This method must be invoked from the dispatcher thread
What it means
WindowManager is single-threaded by design: all its public APIs must run on the WPF dispatcher thread that owns the underlying Dispatcher. CheckDispatcher compares the dispatcher's thread with the current thread and throws InvalidOperationException on mismatch.
Solutions
- Marshal the call to the dispatcher: dispatcher.Invoke(() => windowManager.ShowMainWindow(w)) or Dispatcher.InvokeAsync
- Store the UI Dispatcher at startup and always route window operations through it
- Use a helper like Application.Current.Dispatcher.InvokeAsync for window operations
- Fix the calling code so window management stays on the UI thread (await on UI SynchronizationContext)
Example fix
// before (background thread) windowManager.ShowBlockingWindow(dialog); // after Application.Current.Dispatcher.Invoke(() => windowManager.ShowBlockingWindow(dialog));
Defensive patterns
Strategy: try-catch
Validate before calling
bool onDispatcher = Application.Current.Dispatcher.CheckAccess();
Try / catch
if (Application.Current.Dispatcher.CheckAccess()) DoWindowWork(); else Application.Current.Dispatcher.Invoke(DoWindowWork);
Prevention
- Always marshal window operations via Dispatcher.Invoke/InvokeAsync
- Avoid touching WindowManager from background threads or async continuations on the pool
- Enable thread checks in debug to catch violations early
When it happens
Trigger: Calling ShowMainWindow, ShowBlockingWindow, or any other WindowManager method from a background thread, Task.Run body, or non-UI event (network callback, timer thread).
Common situations: Showing a dialog from async work or a worker thread; showing a window from a background service callback; tests invoking WindowManager off the STA UI thread.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- A dispatcher lock must be created from a different thread…
- The current thread was expected to be the dispatcher thread.
- The current thread was expected to be different from the…
- Trying to lock while another thread owns the lock.
- Trying to unlock while another thread owns the lock.
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/246bccc8b6d9f83a.
Report an issue: GitHub.
Appendix: source
Thrown at sources/presentation/Stride.Core.Presentation.Wpf/Windows/WindowManager.cs:263
{
var window = windowInfo.Window;
if (window == null)
return false;
var location = window.GetType().Assembly.Location;
if (string.IsNullOrEmpty(location))
return false; // dynamic/single-file assembly: can't tell, assume ours to avoid hiding real dialogs
return !location.StartsWith(AppContext.BaseDirectory, StringComparison.OrdinalIgnoreCase);
}
private static void CheckDispatcher()
{
if (dispatcher.Thread != Thread.CurrentThread)
{
const string message = "This method must be invoked from the dispatcher thread";
Logger.Error(message);
throw new InvalidOperationException(message);
}
}
private static void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
if (hwnd == IntPtr.Zero)
return;
var rootHwnd = NativeHelper.GetAncestor(hwnd, NativeHelper.GetAncestorFlags.GetRoot);
if (rootHwnd != IntPtr.Zero && rootHwnd != hwnd)
{
Logger.Debug($"Discarding non-root window ({hwnd}) - root: ({NativeHelper.GetAncestor(hwnd, NativeHelper.GetAncestorFlags.GetRoot)})");
return;
}
// idObject == 0 means it is the window itself, not a child object
if (eventType == NativeHelper.EVENT_OBJECT_SHOW && idObject == 0)
{View on GitHub (pinned to 96fad776d2)