stride3d/stride · error · InvalidOperationException
This code must not be executed in the game thread.
Error message
This code must not be executed in the game thread.
What it means
The inverse guard of EnsureGameAccess: called with inGameThread: false, it asserts the code is NOT running on the sceneGameThread. Operations that must not touch the game thread from within itself (e.g. marshaling work that would deadlock, or asset-side-only mutations) throw InvalidOperationException when invoked from the game thread.
Solutions
- Move the offending call off the game thread, e.g. schedule it via Editor.Dispatcher.InvokeAsync.
- If already in async code, use Task.Run or configure continuations off the game thread.
- Check the called method's documentation for thread requirements and respect the asset-side/game-side split.
Example fix
// before (inside game-thread callback) editor.Dispatcher.Invoke(() => UpdateAssetSide()); // deadlock risk / forbidden // after Task.Run(() => editor.Dispatcher.InvokeAsync(() => UpdateAssetSide()));
Defensive patterns
Strategy: validation
Validate before calling
if (Thread.CurrentThread == controller.GameThread)
throw new InvalidOperationException("This call must be marshaled off the game thread."); Try / catch
try { AssetSideOperation(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("must not be executed")) { await editor.Dispatcher.InvokeAsync(AssetSideOperation); } Prevention
- Never make synchronous calls back into asset/dispatcher code from game-thread callbacks
- Schedule cross-side work via Dispatcher.InvokeAsync or Task.Run
- Document thread requirements on your service APIs
When it happens
Trigger: Calling an API that internally invokes EnsureGameAccess(false) (e.g. EnsureAccess with opposite polarity) while already executing on sceneGameThread — typically game-thread code trying to synchronously call back into asset-side or dispatcher code.
Common situations: Game-side event handlers or callbacks making synchronous calls into services that forbid game-thread execution; recursive Invoke patterns that would deadlock the game loop.
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
- This code must be executed in the game thread.
- Trying to unlock while another thread owns the lock.
- A dispatcher lock must be created from a different thread…
- Trying to lock while another thread owns the lock.
- This controller is beeing disposed.
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/46f125c5106d6de6.
Report an issue: GitHub.
Appendix: source
Thrown at sources/editor/Stride.Assets.Presentation/AssetEditors/GameEditor/Services/EditorGameController.cs:363
/// Verifies that the current thread is the game thread.
/// </summary>
/// <returns><c>True</c> if the current thread is the game thread, <c>False</c> otherwise.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool CheckGameAccess()
{
return Thread.CurrentThread == sceneGameThread;
}
/// <summary>
/// Ensures that the current thread is the game thread. This method will throw an exception if it is not the case.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void EnsureGameAccess(bool inGameThread = true)
{
if (inGameThread && Thread.CurrentThread != sceneGameThread)
throw new InvalidOperationException("This code must be executed in the game thread.");
if (!inGameThread && Thread.CurrentThread == sceneGameThread)
throw new InvalidOperationException("This code must not be executed in the game thread.");
}
/// <inheritdoc/>
bool IDispatcherService.CheckAccess() => CheckGameAccess();
/// <inheritdoc/>
void IDispatcherService.EnsureAccess(bool inDispatcherThread) => EnsureGameAccess(inDispatcherThread);
/// <inheritdoc/>
void IDispatcherService.Invoke(Action callback)
{
throw new NotSupportedException();
}
/// <inheritdoc/>
TResult IDispatcherService.Invoke<TResult>(Func<TResult> callback)
{
throw new NotSupportedException();View on GitHub (pinned to 96fad776d2)