cefsharp/CefSharp · error · InvalidOperationException

This method can only be called on the CEF UI thread.Use Cef.

Error message

This method can only be called on the CEF UI thread.Use Cef.UIThreadTaskFactory to marshal your call onto the CEF UI Thread.

What it means

Thrown by the DestroyWindow extension method when the calling thread is not the CEF UI thread (CefThreadIds.TID_UI). DestroyWindow P/Invokes the native user32 DestroyWindow against the browser HWND; Win32 requires this be called on the thread that owns the window, which for CEF is the UI thread. Calling from any other thread would silently misbehave or deadlock, so CefSharp enforces the thread affinity explicitly.

Source

Thrown at CefSharp.WinForms/WebBrowserExtensions.cs:41

        /// This will avoid the WM_Close message that CEF sends by default to the top level window.
        /// (Which closes your application). This method should generally only be used in the WinForms version.
        /// </summary>
        /// <param name="chromiumWebBrowser">the <see cref="ChromiumWebBrowser"/> or <see cref="ChromiumHostControl"/> instance.</param>
        /// <returns>If the function succeeds, the return value is true.</returns>
        /// <example>
        /// <code>
        /// //Invoke on the CEF UI Thread
        /// Cef.UIThreadTaskFactory.StartNew(() =>
        /// {
        ///   var closed = chromiumWebBrowser.DestroyWindow();
        /// });
        /// </code>
        /// </example>
        public static bool DestroyWindow(this IChromiumWebBrowserBase chromiumWebBrowser)
        {
            if (!Cef.CurrentlyOnThread(CefThreadIds.TID_UI))
            {
                throw new InvalidOperationException("This method can only be called on the CEF UI thread." +
                    "Use Cef.UIThreadTaskFactory to marshal your call onto the CEF UI Thread.");
            }

            if (chromiumWebBrowser.IsDisposed)
            {
                return false;
            }

            var browser = chromiumWebBrowser.BrowserCore;

            if (browser == null)
            {
                return false;
            }

            var handle = browser.GetHost().GetWindowHandle();

View on GitHub (pinned to 16bc6e0711)

Solutions

  1. Marshal the call onto the CEF UI thread via `Cef.UIThreadTaskFactory.StartNew(() => browser.DestroyWindow())`.
  2. Confirm with `Cef.CurrentlyOnThread(CefThreadIds.TID_UI)` before invoking if unsure.
  3. Prefer the higher-level browser close APIs (CloseBrowser) unless you specifically need the raw Win32 DestroyWindow semantics.
  4. Do not call from async void event handlers that may resume on a thread-pool thread.

Example fix

// before
browser.DestroyWindow(); // throws: not on CEF UI thread

// after
Cef.UIThreadTaskFactory.StartNew(() => browser.DestroyWindow());
Defensive patterns

Strategy: validation

Validate before calling

if (Cef.CurrentlyOnThread(CefThreadIds.TID_UI))
{
    browser.DestroyWindow();
}
else
{
    Cef.UIThreadTaskFactory.StartNew(() => browser.DestroyWindow());
}

Prevention

When it happens

Trigger: Calling chromiumWebBrowser.DestroyWindow() directly from a background task, an async continuation, a non-UI event handler, or the main WinForms/WPF UI thread when CEF runs its UI thread separately.

Common situations: Attempting to forcibly close the browser from a Task.Run or a Dispose handler running on a worker thread; mixing the application UI thread with the CEF UI thread.

Related errors


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