WebView2 应用的线程模型

支持的平台:Win32、Windows 窗体、WinUI、WPF。

WebView2 控件基于 组件对象模型 (COM) ,并且必须在 单线程单元 (STA) 线程上运行。

线程安全性

必须在使用消息泵的 UI 线程上创建 WebView2。 所有回调都发生在该线程上,并且必须在该线程上完成对 WebView2 的请求。 从另一个线程使用 WebView2 是不安全的。

唯一的例外是 的 ContentCoreWebView2WebResourceRequest属性。 从 Content 后台线程读取属性流。 流应该是敏捷的,或者应该从后台 STA 创建,以防止 UI 线程的性能下降。

对象属性是单线程的。 例如,从线程以外的Main调用CoreWebView2CookieManager.GetCookiesAsync(null)将成功 (即) 返回 Cookie;但是,尝试在此类调用后访问 cookie 的属性 ((如c.Domain) )将引发异常。

重入性

回调(包括事件处理程序和完成处理程序)以串行方式运行。 运行事件处理程序并开始消息循环后,无法以重新进入的方式运行事件处理程序或完成回调。 如果 WebView2 应用尝试在 WebView2 事件处理程序中同步创建嵌套消息循环或模式 UI,则此方法会导致尝试重新进入。 WebView2 不支持这种重新进入,并且会无限期地将事件处理程序保留在堆栈中。

例如,不支持以下编码方法:

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.
   }
}

相反,请计划相应的工作,以在事件处理程序完成后进行,如以下代码所示:

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);
   }
}

注意

对于 WinForms 和 WPF 应用,若要获取用于调试的完整调用堆栈,必须为 WebView2 应用启用本机代码调试,如下所示:

  1. 在 Visual Studio 中打开 WebView2 项目。
  2. 解决方案资源管理器中,右键单击“WebView2”项目,然后选择“属性”。
  3. 选择“ 调试 ”选项卡,然后选中 “启用本机代码调试 ”复选框,如下所示。

在 Visual Studio 中启用本机代码调试

延期

某些 WebView2 事件读取在相关事件参数上设置的值,或在事件处理程序完成后启动某些操作。 如果还需要运行异步操作(如事件处理程序),请对关联事件的事件参数使用 GetDeferral 方法。 返回Deferral的对象可确保在请求 的 方法Deferral之前Complete,事件处理程序不会被视为已完成。

例如,可以使用 NewWindowRequested 事件提供 , CoreWebView2 以便在事件处理程序完成时作为子窗口进行连接。 但是,如果需要异步创建 CoreWebView2,则应对 NewWindowRequestedEventArgs调用 GetDeferral 方法。 异步创建 CoreWebView2 并在 上NewWindowRequestedEventArgs设置 NewWindow 属性后,对 Deferral 方法GetDeferral返回的对象调用 Complete

C 中的延迟#

Deferral在 C# 中使用 时,最佳做法是将其与块一using起使用。 块 using 可确保完成 , Deferral 即使块中间 using 引发了异常也是如此。 相反,如果有代码来显式调用 Complete,但在调用发生之前 Complete 会引发异常,则延迟直到一段时间后垃圾回收器最终收集和释放延迟才会完成。 在此期间,WebView2 将等待应用代码处理事件。

例如,不要执行以下操作,因为如果在调用 Complete之前出现异常,该 WebResourceRequested 事件不会被视为“已处理”,并阻止 WebView2 呈现该 Web 内容。

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();
}

请改用 using 块,如以下示例所示。 无论是否存在异常,块 usingDeferral 确保 已完成。

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);
   }
}

阻止 UI 线程

WebView2 依赖于 UI 线程的消息泵来运行事件处理程序回调和异步方法完成回调。 如果使用阻止消息泵的方法(如 Task.ResultWaitForSingleObject),则 WebView2 事件处理程序和异步方法完成处理程序不会运行。 例如,以下代码未完成,因为在 Task.Result 等待 ExecuteScriptAsync 完成时停止消息泵。 由于消息泵被阻止, ExecuteScriptAsync 因此 无法完成。

例如,以下代码不起作用,因为它使用 Task.Result

private void Button_Click(object sender, EventArgs e)
{
    string result = webView2Control.CoreWebView2.ExecuteScriptAsync("'test'").Result;
    MessageBox.Show(this, result, "Script Result");
}

请改用和 awaitasync异步await机制,这不会阻止消息泵或 UI 线程。 例如:

private async void Button_Click(object sender, EventArgs e)
{
    string result = await webView2Control.CoreWebView2.ExecuteScriptAsync("'test'");
    MessageBox.Show(this, result, "Script Result");
}

另请参阅