cefsharp/CefSharp · error · DevToolsClientException

Unable to invoke ExecuteDevToolsMethod on CEF UI Thread.

Error message

Unable to invoke ExecuteDevToolsMethod on CEF UI Thread.

What it means

Thrown by ExecuteDevToolsMethodAsync when the call is not already on the CEF UI Thread and CefThread.CanExecuteOnUiThread is false. CanExecuteOnUiThread is only true while CEF is initialized and its UI thread message loop is running. Outside that window (before Cef.Initialize, after Cef.Shutdown, or during shutdown) there is no valid thread to dispatch the native call to, so CefSharp aborts cleanly.

Source

Thrown at CefSharp.Core/DevTools/DevToolsClient.cs:175

            }

            //Currently on CEF UI Thread we can directly execute
            if (CefThread.CurrentlyOnUiThread)
            {
                ExecuteDevToolsMethod(browserHost, messageId, method, parameters, methodResultContext);
            }
            //ExecuteDevToolsMethod can only be called on the CEF UI Thread
            else if (CefThread.CanExecuteOnUiThread)
            {
                CefThread.ExecuteOnUiThread(() =>
                {
                    ExecuteDevToolsMethod(browserHost, messageId, method, parameters, methodResultContext);
                });
            }
            else
            {
                queuedCommandResults.TryRemove(messageId, out methodResultContext);
                throw new DevToolsClientException("Unable to invoke ExecuteDevToolsMethod on CEF UI Thread.");
            }

            return taskCompletionSource.Task;
        }

        private void ExecuteDevToolsMethod(IBrowserHost browserHost, int messageId, string method, IDictionary<string, object> parameters, DevToolsMethodResponseContext methodResultContext)
        {
            try
            {
                var returnedMessageId = browserHost.ExecuteDevToolsMethod(messageId, method, parameters);
                if (returnedMessageId == 0)
                {
                    throw new DevToolsClientException(string.Format("Failed to execute dev tools method {0}.", method));
                }
                else if (returnedMessageId != messageId)
                {
                    //For some reason our message Id's don't match
                    throw new DevToolsClientException(string.Format("Generated MessageId {0} doesn't match returned Message Id {1}", returnedMessageId, messageId));

View on GitHub (pinned to 16bc6e0711)

Solutions

  1. Ensure all DevTools calls happen strictly between Cef.Initialize completion and Cef.Shutdown - gate calls on a boolean flag you set/clear around Initialize/Shutdown.
  2. Cancel outstanding async DevTools work (CancellationToken) before calling Cef.Shutdown so no in-flight call lands after the loop stops.
  3. Move early-startup DevTools usage into a Cef.IsInitialized callback / the browser's IsBrowserInitializedChanged event rather than inline at startup.
  4. Wrap shutdown-path calls in try/catch for DevToolsClientException and ignore them as expected.

Example fix

// before - fires during/after shutdown
await client.Network.DisableAsync(); // may run after Cef.Shutdown begins

// after - gate on liveness flag
private static volatile bool _cefAlive;
// set _cefAlive = true after Cef.Initialize; false before Cef.Shutdown
if (!_cefAlive) return;
try { await client.Network.DisableAsync(); }
catch (DevToolsClientException) { /* shutdown race, ignore */ }
Defensive patterns

Strategy: validation

Validate before calling

// Gate DevTools calls on a flag you control around Cef lifecycle.
private static volatile bool _cefAlive;
// set true after Cef.Initialize completes; false before Cef.Shutdown
if (!_cefAlive) return;
await client.Network.DisableAsync();

Type guard

public static bool CanCallDevToolsNow() => _cefAlive && CefThread.CanExecuteOnUiThread;

Try / catch

try { await client.Network.DisableAsync(); }
catch (DevToolsClientException ex) when (ex.Message.Contains("CEF UI Thread"))
{ /* shutdown race - ignore */ }

Prevention

When it happens

Trigger: Issuing a DevTools command before Cef.Initialize has completed (e.g. in a static constructor or very early startup); issuing one after Cef.Shutdown has started; calling from a background thread during application exit; calling from a finalizer/destructor that runs after shutdown.

Common situations: Fire-and-forget DevTools calls queued during shutdown that execute after the message loop stops; unit tests that construct a DevToolsClient without fully initializing CEF; background service code that outlives the browser session.

Related errors


AI-assisted analysis of cefsharp/CefSharp@16bc6e0711 (2026-08-13). Data as JSON: /api/errors/6cf8d1b4b74d87a2. Report an issue: GitHub.