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

  1. Move the offending call off the game thread, e.g. schedule it via Editor.Dispatcher.InvokeAsync.
  2. If already in async code, use Task.Run or configure continuations off the game thread.
  3. 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

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


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)