{"record":{"id":"41dcf012409a89ae","repo":"stride3d/stride","slug":"this-code-must-be-executed-in-the-game-thread","errorCode":null,"errorMessage":"This code must be executed in the game thread.","messagePattern":"This code must be executed in the game thread\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/editor/Stride.Assets.Presentation/AssetEditors/GameEditor/Services/EditorGameController.cs","lineNumber":361,"sourceCode":"\n        /// <summary>\n        /// Verifies that the current thread is the game thread.\n        /// </summary>\n        /// <returns><c>True</c> if the current thread is the game thread, <c>False</c> otherwise.</returns>\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        public bool CheckGameAccess()\n        {\n            return Thread.CurrentThread == sceneGameThread;\n        }\n\n        /// <summary>\n        /// Ensures that the current thread is the game thread. This method will throw an exception if it is not the case.\n        /// </summary>\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        public void EnsureGameAccess(bool inGameThread = true)\n        {\n            if (inGameThread && Thread.CurrentThread != sceneGameThread)\n                throw new InvalidOperationException(\"This code must be executed in the game thread.\");\n            if (!inGameThread && Thread.CurrentThread == sceneGameThread)\n                throw new InvalidOperationException(\"This code must not be executed in the game thread.\");\n        }\n\n        /// <inheritdoc/>\n        bool IDispatcherService.CheckAccess() => CheckGameAccess();\n\n        /// <inheritdoc/>\n        void IDispatcherService.EnsureAccess(bool inDispatcherThread) => EnsureGameAccess(inDispatcherThread);\n\n        /// <inheritdoc/>\n        void IDispatcherService.Invoke(Action callback)\n        {\n            throw new NotSupportedException();\n        }\n\n        /// <inheritdoc/>\n        TResult IDispatcherService.Invoke<TResult>(Func<TResult> callback)","sourceCodeStart":343,"sourceCodeEnd":379,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/editor/Stride.Assets.Presentation/AssetEditors/GameEditor/Services/EditorGameController.cs#L343-L379","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nvar part = controller.FindGameSidePart(partId); // wrong thread\n// after\nvar part = await controller.InvokeAsync(() => controller.FindGameSidePart(partId));","handlingStrategy":"validation","validationCode":"if (!controller.CheckGameAccess())\n    await controller.InvokeAsync(() => DoGameSideWork());\nelse\n    DoGameSideWork();","typeGuard":null,"tryCatchPattern":"try { DoGameSideWork(); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"game thread\")) { controller.InvokeAsync(DoGameSideWork); }","preventionTips":["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"],"tags":["threading","thread-affinity","game-editor","invoke"],"backgroundTag":"invalid-state-transition","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}