WebView2 应用的线程模型
支持的平台:Win32、Windows 窗体、WinUI、WPF。
WebView2 控件基于 组件对象模型 (COM) ,并且必须在 单线程单元 (STA) 线程上运行。
线程安全性
必须在使用消息泵的 UI 线程上创建 WebView2。 所有回调都发生在该线程上,并且必须在该线程上完成对 WebView2 的请求。 从另一个线程使用 WebView2 是不安全的。
唯一的例外是 的 Content
CoreWebView2WebResourceRequest
属性。 从 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 应用启用本机代码调试,如下所示:
- 在 Visual Studio 中打开 WebView2 项目。
- 在解决方案资源管理器中,右键单击“WebView2”项目,然后选择“属性”。
- 选择“ 调试 ”选项卡,然后选中 “启用本机代码调试 ”复选框,如下所示。
延期
某些 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
块,如以下示例所示。 无论是否存在异常,块 using
都 Deferral
确保 已完成。
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.Result
或 WaitForSingleObject
),则 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");
}
请改用和 await
等async
异步await
机制,这不会阻止消息泵或 UI 线程。 例如:
private async void Button_Click(object sender, EventArgs e)
{
string result = await webView2Control.CoreWebView2.ExecuteScriptAsync("'test'");
MessageBox.Show(this, result, "Script Result");
}
另请参阅
- WebView2 入门
- WebView2Samples 存储库 - WebView2 功能的综合示例。
- WebView2 API 参考