Compartir vía


Modelo de subprocesos para aplicaciones WebView2

Plataformas admitidas: Win32, Windows Forms, WinUI, WPF.

El control WebView2 se basa en el modelo de objetos de componente (COM) y debe ejecutarse en un subproceso de Apartamentos con subproceso único (STA ).

Seguridad para subprocesos

WebView2 debe crearse en un subproceso de interfaz de usuario que use una bomba de mensajes. Todas las devoluciones de llamada se producen en ese subproceso y las solicitudes en WebView2 deben realizarse en ese subproceso. No es seguro usar WebView2 desde otro subproceso.

La única excepción es para la Content propiedad de CoreWebView2WebResourceRequest. El Content flujo de propiedades se lee desde un subproceso en segundo plano. La secuencia debe ser ágil o debe crearse a partir de un STA en segundo plano, para evitar la degradación del rendimiento del subproceso de interfaz de usuario.

Las propiedades del objeto son de un solo subproceso. Por ejemplo, la llamada CoreWebView2CookieManager.GetCookiesAsync(null) desde un subproceso distinto Main de se realizará correctamente (es decir, se devuelven cookies); sin embargo, al intentar acceder a las propiedades de las cookies (como c.Domain) después de dicha llamada se producirá una excepción.

Reentrancy

Las devoluciones de llamada, incluidos los controladores de eventos y los controladores de finalización, se ejecutan en serie. Después de ejecutar un controlador de eventos y comenzar un bucle de mensajes, no se puede ejecutar un controlador de eventos o una devolución de llamada de finalización de forma que vuelva a entrar. Si una aplicación WebView2 intenta crear un bucle de mensajes anidado o una interfaz de usuario modal de forma sincrónica dentro de un controlador de eventos WebView2, este enfoque conduce a un intento de reentrada. Este tipo de reentrancy no se admite en WebView2 y dejaría el controlador de eventos en la pila indefinidamente.

Por ejemplo, no se admite el siguiente enfoque de codificación:

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

En su lugar, programe el trabajo adecuado para que tenga lugar después de completar el controlador de eventos, como se muestra en el código siguiente:

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

Nota:

Para winforms y aplicaciones WPF, para obtener la pila de llamadas completa con fines de depuración, debe activar la depuración de código nativo para aplicaciones WebView2, como se indica a continuación:

  1. Abra el proyecto WebView2 en Visual Studio.
  2. En Explorador de soluciones, haga clic con el botón derecho en el proyecto WebView2 y, a continuación, seleccione Propiedades.
  3. Seleccione la pestaña Depurar y, a continuación, active la casilla Habilitar depuración de código nativo , como se muestra a continuación.

Habilitación de la depuración de código nativo en Visual Studio

Aplazamientos

Algunos eventos WebView2 leen valores que se establecen en los argumentos de evento relacionados o inician alguna acción una vez completado el controlador de eventos. Si también necesita ejecutar una operación asincrónica, como un controlador de eventos, use el GetDeferral método en los argumentos de evento de los eventos asociados. El objeto devuelto Deferral garantiza que el controlador de eventos no se considere completo hasta que se solicite el Complete método de Deferral .

Por ejemplo, puede usar el NewWindowRequested evento para proporcionar un CoreWebView2 objeto para conectarse como una ventana secundaria cuando se complete el controlador de eventos. Pero si necesita crear de forma asincrónica , CoreWebView2debe llamar al GetDeferral método en NewWindowRequestedEventArgs. Después de crear CoreWebView2 de forma asincrónica y establecer la NewWindow propiedad en , NewWindowRequestedEventArgsllame al CompleteDeferral objeto devuelto por el GetDeferral método .

Aplazamientos en C#

Cuando se usa en Deferral C#, el procedimiento recomendado es usarlo con un using bloque. El using bloque garantiza que Deferral se complete incluso si se produce una excepción en medio del using bloque. Si, en su lugar, tiene código para llamar Completeexplícitamente a , pero se produce una excepción antes Complete de que se produzca la llamada, el aplazamiento no se completa hasta algún tiempo más tarde, cuando el recolector de elementos no utilizados finalmente recopila y elimina el aplazamiento. Entre tanto, WebView2 espera a que el código de la aplicación controle el evento.

Por ejemplo, no haga lo siguiente, porque si hay una excepción antes de llamar a Complete, el WebResourceRequested evento no se considera "controlado" y impide que WebView2 representa ese contenido 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();
}

En su lugar, use un using bloque, como en el ejemplo siguiente. El using bloque garantiza que Deferral se haya completado, independientemente de si hay o no una excepción.

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

Bloquear el subproceso de interfaz de usuario

WebView2 se basa en la bomba de mensajes del subproceso de interfaz de usuario para ejecutar devoluciones de llamada del controlador de eventos y devoluciones de llamada de finalización del método asincrónico. Si usa métodos que bloquean la bomba de mensajes, como Task.Result o WaitForSingleObject, los controladores de eventos WebView2 y los controladores de finalización de métodos asincrónicos no se ejecutan. Por ejemplo, el código siguiente no se completa, porque Task.Result detiene la bomba de mensajes mientras espera ExecuteScriptAsync a que se complete. Dado que la bomba de mensajes está bloqueada, ExecuteScriptAsync no puede completarse.

Por ejemplo, el código siguiente no funciona, porque usa Task.Result.

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

En su lugar, use un mecanismo asincrónico await como async y await, que no bloquea la bomba de mensajes ni el subproceso de interfaz de usuario. Por ejemplo:

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

Vea también