dotnet/wpf · error · InvalidOperationException

Processing is disabled while the Dispatcher is in this…

Error message

Processing is disabled while the Dispatcher is in this state.

What it means

WPF's Dispatcher prohibits re-entrant message processing while processing is disabled (when _disableProcessingCount > 0, set via Dispatcher.DisableProcessing). If a window message arrives and the WndProcHook is entered while processing is disabled, the Dispatcher throws InvalidOperationException because pumping messages in a disabled state would violate its reentrancy guarantees.

Solutions

  1. Remove the surrounding DisableProcessing/EnableProcessing scope before any call that can pump messages
  2. Ensure DisableProcessing is only used for short, non-pumping critical sections
  3. Use Dispatcher.PushFrame-free alternatives (e.g. async/await) instead of blocking waits inside disabled regions
  4. Wrap the region in try/finally so EnableProcessing is always called and the count does not leak

Example fix

// before
using (Dispatcher.CurrentDispatcher.DisableProcessing())
{
    MessageBox.Show("done"); // pumps messages -> throws
}
// after
MessageBox.Show("done");
using (Dispatcher.CurrentDispatcher.DisableProcessing())
{
    DoCriticalWork();
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Only pump-free work inside disabled regions
bool safe = Dispatcher.CurrentDispatcher.DisableProcessingCountIsZero(); // custom check via reflection if needed

Try / catch

try
{
    workThatMayPump();
}
catch (InvalidOperationException ex) when (ex.Message.Contains("Processing is disabled"))
{
    logger.LogWarning("Attempted message pumping while dispatcher processing disabled");
}

Prevention

When it happens

Trigger: Calling Dispatcher.DisableProcessing() and then performing an operation that pumps messages (e.g. showing a modal dialog, waiting on a dispatcher operation) so WndProcHook receives a message while _disableProcessingCount > 0.

Common situations: Developers disabling dispatcher processing to block background reentrancy but then calling APIs that implicitly pump messages (MessageBox, drag-drop, Wait on a DispatcherOperation) on the dispatcher thread.

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 dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/9186f2dc11bc7fcb. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/Dispatcher.cs:2213

        private void TranslateAndDispatchMessage(ref MSG msg)
        {
            bool handled = false;

            handled = ComponentDispatcher.RaiseThreadMessage(ref msg);

            if(!handled)
            {
                UnsafeNativeMethods.TranslateMessage(ref msg);
                UnsafeNativeMethods.DispatchMessage(ref msg);
            }
        }

        private IntPtr WndProcHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            WindowMessage message = (WindowMessage)msg;
            if(_disableProcessingCount > 0)
            {
                throw new InvalidOperationException(SR.DispatcherProcessingDisabledButStillPumping);
            }

            if(message == WindowMessage.WM_DESTROY)
            {
                if(!_hasShutdownStarted && !_hasShutdownFinished) // Dispatcher thread - no lock needed for read
                {
                    // Aack!  We are being torn down rudely!  Try to
                    // shut the dispatcher down as nicely as we can.
                    ShutdownImpl();
                }
            }
            else if(message == _msgProcessQueue)
            {
                ProcessQueue();
            }
            else if(message == WindowMessage.WM_TIMER && (int) wParam == TIMERID_BACKGROUND)
            {
                // This timer is just used to process background operations.

View on GitHub (pinned to 81131a70a4)