{"record":{"id":"63dca2bb4edfad37","repo":"dotnet/wpf","slug":"the-thread-may-not-wait-on-operations-that-are-already","errorCode":null,"errorMessage":"The thread may not wait on operations that are already executing on the same thread.","messagePattern":"The thread may not wait on operations that are already executing on the same thread\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.cs","lineNumber":194,"sourceCode":"        /// <returns>\n        ///     The status of the operation.  To obtain the return value\n        ///     of the invoked delegate, use the the Result property.\n        /// </returns>\n        public DispatcherOperationStatus Wait(TimeSpan timeout)\n        {\n            if((_status == DispatcherOperationStatus.Pending || _status == DispatcherOperationStatus.Executing) &&\n                timeout.TotalMilliseconds != 0)\n            {\n                if(_dispatcher.Thread == Thread.CurrentThread)\n                {\n                    if(_status == DispatcherOperationStatus.Executing)\n                    {\n                        // We are the dispatching thread, and the current operation state is\n                        // executing, which means that the operation is in the middle of\n                        // executing (on this thread) and is trying to wait for the execution\n                        // to complete.  Unfortunately, the thread will now deadlock, so\n                        // we throw an exception instead.\n                        throw new InvalidOperationException(SR.ThreadMayNotWaitOnOperationsAlreadyExecutingOnTheSameThread);\n                    }\n                    \n                    // We are the dispatching thread for this operation, so\n                    // we can't block.  We will push a frame instead.\n                    DispatcherOperationFrame frame = new DispatcherOperationFrame(this, timeout);\n                    Dispatcher.PushFrame(frame);\n                }\n                else\n                {\n                    // We are some external thread, so we can just block.  Of\n                    // course this means that the Dispatcher (queue)for this\n                    // thread (if any) is now blocked.  The COM STA model \n                    // suggests that we should pump certain messages so that\n                    // back-communication can happen.  Underneath us, the CLR\n                    // will pump the STA apartment for us, and we will allow \n                    // the UI thread for a context to call\n                    // Invoke(Priority.Max, ...) without going through the\n                    // blocked queue.","sourceCodeStart":176,"sourceCodeEnd":212,"githubUrl":"https://github.com/dotnet/wpf/blob/81131a70a4c573cd62748a5c36908fc4d662daa9/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.cs#L176-L212","documentation":"DispatcherOperation.Wait throws InvalidOperationException when the thread that is currently dispatching the operation tries to wait on that same operation while it is executing. Blocking would deadlock the thread, so WPF detects the condition and throws instead of hanging.","triggerScenarios":"Calling Wait() (or EndInvoke/Result) on a DispatcherOperation from inside the delegate that the operation is currently executing on the same Dispatcher thread.","commonSituations":"A queued work item waits on its own operation's result, or nested code inside a Dispatcher.Invoke callback calls Wait on the enclosing operation; typical in legacy code migrated to TPL where blocking waits remain.","solutions":["Do not wait on an operation from within its own executing delegate - restructure so the continuation happens after execution","Use async/await on the DispatcherOperation (await operation.GetAwaiter().GetResult() replaced by await operation.Task) instead of blocking","If work must run after the operation completes, chain a continuation (ContinueWith / InvokeAsync) instead of blocking","Split the work so the waiting code runs on a different thread or after PushFrame returns"],"exampleFix":"// before (inside the operation's delegate)\nDispatcherOperation op = Dispatcher.CurrentDispatcher.InvokeAsync(() => Work());\nop.Wait(); // throws: waiting on own operation\n// after\nawait Dispatcher.CurrentDispatcher.InvokeAsync(() => Work());","handlingStrategy":"validation","validationCode":"bool isDeadlockRisk(DispatcherOperation op) =>\n    op.Status == DispatcherOperationStatus.Executing &&\n    op.Dispatcher.Thread == Thread.CurrentThread;","typeGuard":"bool canWaitSafely(DispatcherOperation op) =>\n    !(op.Status == DispatcherOperationStatus.Executing && op.Dispatcher.Thread == Thread.CurrentThread);","tryCatchPattern":"try\n{\n    operation.Wait(timeout);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"may not wait\"))\n{\n    // restructure: do not wait on your own operation\n}","preventionTips":["Never Wait()/block on a DispatcherOperation from inside its own delegate","Use await operation.Task instead of blocking Wait","Chain continuations instead of blocking","Run blocking waits only on non-dispatcher threads"],"tags":["deadlock","dispatcher","threading","wpf"],"backgroundTag":"invalid-state-transition","analyzedSha":"81131a70a4c573cd62748a5c36908fc4d662daa9","analyzedAt":"2026-09-14T10:12:48.479Z","contentChangedAt":"2026-09-14T10:12:48.479Z","schemaVersion":2},"datasetVersion":"2026-09-21T21:30:21.729Z"}