Threading model for WebView2 apps
Supported platforms: Win32, Windows Forms, WinUI, WPF.
The WebView2 control is based on the Component Object Model (COM) and must run on a Single Threaded Apartments (STA) thread.
Thread safety
The WebView2 must be created on a UI thread that uses a message pump. All callbacks occur on that thread, and requests into the WebView2 must be done on that thread. It isn't safe to use the WebView2 from another thread.
The only exception is for the Content
property of CoreWebView2WebResourceRequest
. The Content
property stream is read from a background thread. The stream should be agile or should be created from a background STA, to prevent performance degradation of the UI thread.
Object properties are single-threaded. For example, calling CoreWebView2CookieManager.GetCookiesAsync(null)
from a thread other than Main
will succeed (that is, cookies are returned); however, attempting to access the cookies' properties (such as c.Domain
) after such a call will throw an exception.
Reentrancy
Callbacks, including event handlers and completion handlers, run serially. After you run an event handler and begin a message loop, an event handler or completion callback cannot be run in a re-entrant manner. If a WebView2 app tries to create a nested message loop or modal UI synchronously within a WebView2 event handler, this approach leads to attempted reentrancy. Such reentrancy isn't supported in WebView2 and would leave the event handler in the stack indefinitely.
For example, the following coding approach isn't supported:
private void Btn_Click(object sender, EventArgs e)
{
// Post web message when button is clicked
this.webView2Control.ExecuteScriptAsync("window.chrome.webview.postMessage(\"Open Dialog\");");
}
private void CoreWebView2_WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e)
{
string msg = e.TryGetWebMessageAsString();
if (msg == "Open Dialog")
{
Form1 form = new Form1(); // Create a new form that contains a new WebView2 instance when web message is received.
form.ShowDialog(); // This will cause a reentrancy issue and cause the newly created WebView2 control inside the modal dialog to hang.
}
}
Instead, schedule the appropriate work to take place after completion of the event handler, as shown in the following code:
private void CoreWebView2_WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e)
{
string msg = e.TryGetWebMessageAsString();
if (msg == "Open Dialog")
{
// Show a modal dialog after the current event handler is completed, to avoid potential reentrancy caused by running a nested message loop in the WebView2 event handler.
System.Threading.SynchronizationContext.Current.Post((_) => {
Form1 form = new Form1();
form.ShowDialog();
form.Closed();
}, null);
}
}
Note
For WinForms and WPF apps, to get the full call stack for debugging purposes, you must turn on native code debugging for WebView2 apps, as follows:
- Open your WebView2 project in Visual Studio.
- In Solution Explorer, right-click the WebView2 project and then select Properties.
- Select the Debug tab, and then select the Enable native code debugging checkbox, as shown below.
Deferrals
Some WebView2 events read values that are set on the related event arguments, or start some action after the event handler completes. If you also need to run an asynchronous operation, such as an event handler, use the GetDeferral
method on the event arguments of the associated events. The returned Deferral
object ensures that the event handler isn't considered complete until the Complete
method of the Deferral
is requested.
For instance, you can use the NewWindowRequested
event to provide a CoreWebView2
to connect as a child window when the event handler completes. But if you need to asynchronously create the CoreWebView2
, you should call the GetDeferral
method on the NewWindowRequestedEventArgs
. After you've asynchronously created the CoreWebView2
and set the NewWindow
property on the NewWindowRequestedEventArgs
, call Complete
on the Deferral
object that's returned by the GetDeferral
method.
Deferrals in C#
When using a Deferral
in C#, the best practice is to use it with a using
block. The using
block ensures that the Deferral
is completed even if an exception is thrown in the middle of the using
block. If instead, you have code to explicitly call Complete
, but an exception is thrown before your Complete
call occurs, then the deferral isn't completed until some time later, when the garbage collector eventually collects and disposes of the deferral. In the interim, the WebView2 waits for the app code to handle the event.
For example, don't do the following, because if there's an exception before calling Complete
, the WebResourceRequested
event isn't considered "handled", and blocks WebView2 from rendering that web content.
private async void WebView2WebResourceRequestedHandler(CoreWebView2 sender,
CoreWebView2WebResourceRequestedEventArgs eventArgs)
{
var deferral = eventArgs.GetDeferral();
args.Response = await CreateResponse(eventArgs);
// Calling Complete is not recommended, because if CreateResponse
// throws an exception, the deferral isn't completed.
deferral.Complete();
}
Instead, use a using
block, as in the following example. The using
block ensures that the Deferral
is completed, whether or not there's an exception.
private async void WebView2WebResourceRequestedHandler(CoreWebView2 sender,
CoreWebView2WebResourceRequestedEventArgs eventArgs)
{
// The using block ensures that the deferral is completed, regardless of
// whether there's an exception.
using (eventArgs.GetDeferral())
{
args.Response = await CreateResponse(eventArgs);
}
}
Block the UI thread
WebView2 relies on the message pump of the UI thread to run event handler callbacks and async method completion callbacks. If you use methods that block the message pump, such as Task.Result
or WaitForSingleObject
, then your WebView2 event handlers and async-method completion handlers don't run. For example, the following code doesn't complete, because Task.Result
stops the message pump while it waits for ExecuteScriptAsync
to complete. Because the message pump is blocked, the ExecuteScriptAsync
isn't able to complete.
For example, the following code doesn't work, because it uses Task.Result
.
private void Button_Click(object sender, EventArgs e)
{
string result = webView2Control.CoreWebView2.ExecuteScriptAsync("'test'").Result;
MessageBox.Show(this, result, "Script Result");
}
Instead, use an asynchronous await
mechanism such as async
and await
, which doesn't block the message pump or the UI thread. For example:
private async void Button_Click(object sender, EventArgs e)
{
string result = await webView2Control.CoreWebView2.ExecuteScriptAsync("'test'");
MessageBox.Show(this, result, "Script Result");
}
See also
- Get started with WebView2
- WebView2Samples repo - a comprehensive example of WebView2 capabilities.
- WebView2 API reference