{"record":{"id":"288a1304cdaa09b8","repo":"stride3d/stride","slug":"the-current-thread-was-expected-to-be-the-dispatcher-thread","errorCode":null,"errorMessage":"The current thread was expected to be the dispatcher thread.","messagePattern":"The current thread was expected to be the dispatcher thread\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/presentation/Stride.Core.Presentation.Wpf/View/DispatcherService.cs","lineNumber":118,"sourceCode":"        [NotNull]\n        public static Task<TResult> InvokeTask<TResult>([NotNull] Dispatcher dispatcher, Func<Task<TResult>> task, CancellationToken token = default)\n        {\n            var tcs = new TaskCompletionSource<TResult>();\n            dispatcher.InvokeAsync(async () => tcs.SetResult(await task()), DispatcherPriority.Normal, token);\n            return tcs.Task;\n        }\n\n        /// <inheritdoc/>\n        public bool CheckAccess()\n        {\n            return Thread.CurrentThread == dispatcher.Thread;\n        }\n\n        /// <inheritdoc/>\n        public void EnsureAccess(bool inDispatcherThread = true)\n        {\n            if (inDispatcherThread && Thread.CurrentThread != dispatcher.Thread)\n                throw new InvalidOperationException(\"The current thread was expected to be the dispatcher thread.\");\n            if (!inDispatcherThread && Thread.CurrentThread == dispatcher.Thread)\n                throw new InvalidOperationException(\"The current thread was expected to be different from the dispatcher thread.\");\n        }\n    }\n}\n","sourceCodeStart":100,"sourceCodeEnd":124,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/presentation/Stride.Core.Presentation.Wpf/View/DispatcherService.cs#L100-L124","documentation":"DispatcherService.EnsureAccess verifies thread affinity for the dispatcher it wraps. With the default inDispatcherThread=true, it throws InvalidOperationException when called from any thread other than the dispatcher thread, protecting WPF objects that can only be touched by their owning thread.","triggerScenarios":"Calling EnsureAccess() (or EnsureAccess(true)) from a background/worker thread; performing UI updates inside Task.Run or a background thread callback and asserting dispatcher affinity.","commonSituations":"Updating observable collections or view models from async work or ThreadPool threads; forgetting to marshal work with dispatcher.Invoke/InvokeAsync; unit tests running assertions on non-UI threads.","solutions":["Wrap the affected work in dispatcher.Invoke or dispatcher.InvokeAsync so it runs on the dispatcher thread","If the check intent was the opposite, pass EnsureAccess(false) to require a non-dispatcher thread","Remove or re-scope the EnsureAccess call if the code intentionally runs off-thread and marshals later"],"exampleFix":"// before\nTask.Run(() => { dispatcherService.EnsureAccess(); UpdateUi(); });\n// after\ndispatcherService.InvokeOrBeginInvoke(() => UpdateUi()); // work marshaled onto dispatcher thread","handlingStrategy":"try-catch","validationCode":"if (dispatcherService.CheckAccess()) DoWork(); else dispatcherService.Invoke(() => DoWork());","typeGuard":"bool OnDispatcher(DispatcherService s) => s.CheckAccess();","tryCatchPattern":"try { s.EnsureAccess(); } catch (InvalidOperationException ex) when (ex.Message.Contains(\"expected to be the dispatcher thread\")) { dispatcher.Invoke(Work); }","preventionTips":["Marshal UI work with Invoke/InvokeAsync","Never touch UI-bound state from Task.Run","Use EnsureAccess as assertion during development"],"tags":["wpf","threading","dispatcher"],"backgroundTag":"thread-interrupted","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}