stride3d/stride · error · InvalidOperationException
This code must be executed in the game thread.
Error message
This code must be executed in the game thread.
What it means
EnsureGameAccess(inGameThread: true) asserts that the current thread is the dedicated sceneGameThread that owns the game form/scene. Game-side objects are not thread-safe, so any access to them from another thread would corrupt state; the method throws InvalidOperationException when called from the wrong thread.
Solutions
- Wrap the access in Controller.InvokeAsync(() => ...) so it executes on the game thread.
- If running on the game thread is impossible, marshal the data out via InvokeAsync and operate on copies.
- Add EnsureGameAccess() calls early in your own game-side helpers to catch threading mistakes close to the source.
Example fix
// before var part = controller.FindGameSidePart(partId); // wrong thread // after var part = await controller.InvokeAsync(() => controller.FindGameSidePart(partId));
Defensive patterns
Strategy: validation
Validate before calling
if (!controller.CheckGameAccess())
await controller.InvokeAsync(() => DoGameSideWork());
else
DoGameSideWork(); Try / catch
try { DoGameSideWork(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("game thread")) { controller.InvokeAsync(DoGameSideWork); } Prevention
- Always wrap game-side access in Controller.InvokeAsync
- Keep game-side and asset-side code clearly separated
- Call EnsureGameAccess() at the top of your own game-side helpers
When it happens
Trigger: Calling game-side APIs such as FindGameSidePart or EnsureAccess (anything that routes through EnsureGameAccess) directly from the UI thread or a worker thread without wrapping the call in Controller.InvokeAsync.
Common situations: Plugin code touching the live scene from a WPF event handler; background tasks that computed a change and then tried to apply it directly to game objects; forgetting the InvokeAsync indirection the change propagator normally uses.
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 not be executed in the game thread.
- Trying to unlock while another thread owns the lock.
- Trying to lock while another thread owns the lock.
- A dispatcher lock must be created from a different thread…
- This controller is beeing disposed.
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/41dcf012409a89ae.
Report an issue: GitHub.
Appendix: source
Thrown at sources/editor/Stride.Assets.Presentation/AssetEditors/GameEditor/Services/EditorGameController.cs:361
/// <summary>
/// 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)View on GitHub (pinned to 96fad776d2)