Partage via


Modèle de thread pour les applications WebView2

Plateformes prises en charge : Win32, Windows Forms, WinUI, WPF.

Le contrôle WebView2 est basé sur le modèle COM (Component Object Model) et doit s’exécuter sur un thread STA (Single Threaded Apartments).

Sécurité des threads

WebView2 doit être créé sur un thread d’interface utilisateur qui utilise une pompe de messages. Tous les rappels se produisent sur ce thread, et les requêtes dans webView2 doivent être effectuées sur ce thread. Il n’est pas sûr d’utiliser WebView2 à partir d’un autre thread.

La seule exception concerne la Content propriété de CoreWebView2WebResourceRequest. Le Content flux de propriété est lu à partir d’un thread d’arrière-plan. Le flux doit être agile ou doit être créé à partir d’un sta en arrière-plan pour empêcher la dégradation des performances du thread d’interface utilisateur.

Les propriétés de l’objet sont monothread. Par exemple, l’appel CoreWebView2CookieManager.GetCookiesAsync(null) à partir d’un thread autre que Main réussit (c’est-à-dire que les cookies sont retournés). Toutefois, si vous tentez d’accéder aux propriétés des cookies (telles que c.Domain) après un tel appel, une exception est levée.

Réentrance

Les rappels, y compris les gestionnaires d’événements et les gestionnaires de saisie semi-automatique, s’exécutent en série. Après avoir exécuté un gestionnaire d’événements et commencé une boucle de message, un gestionnaire d’événements ou un rappel de fin ne peut pas être exécuté de manière réinitative. Si une application WebView2 tente de créer une boucle de message imbriquée ou une interface utilisateur modale de façon synchrone dans un gestionnaire d’événements WebView2, cette approche entraîne une tentative de réentrance. Une telle réentrance n’est pas prise en charge dans WebView2 et laisse indéfiniment le gestionnaire d’événements dans la pile.

Par exemple, l’approche de codage suivante n’est pas prise en charge :

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

Au lieu de cela, planifiez le travail approprié après l’achèvement du gestionnaire d’événements, comme indiqué dans le code suivant :

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

Remarque

Pour les applications WinForms et WPF, pour obtenir la pile des appels complète à des fins de débogage, vous devez activer le débogage de code natif pour les applications WebView2, comme suit :

  1. Ouvrez votre projet WebView2 dans Visual Studio.
  2. Dans Explorateur de solutions, cliquez avec le bouton droit sur le projet WebView2, puis sélectionnez Propriétés.
  3. Sélectionnez l’onglet Déboguer , puis cochez la case Activer le débogage du code natif , comme indiqué ci-dessous.

Activation du débogage de code natif dans Visual Studio

Reports

Certains événements WebView2 lisent les valeurs définies sur les arguments d’événement associés ou démarrent une action une fois le gestionnaire d’événements terminé. Si vous devez également exécuter une opération asynchrone, telle qu’un gestionnaire d’événements, utilisez la GetDeferral méthode sur les arguments d’événement des événements associés. L’objet retourné Deferral garantit que le gestionnaire d’événements n’est pas considéré comme terminé tant que la Complete méthode du n’est Deferral pas demandée.

Par instance, vous pouvez utiliser l’événement NewWindowRequested pour fournir un CoreWebView2 pour vous connecter en tant que fenêtre enfant lorsque le gestionnaire d’événements se termine. Toutefois, si vous devez créer de façon asynchrone le CoreWebView2, vous devez appeler la GetDeferral méthode sur le NewWindowRequestedEventArgs. Une fois que vous avez créé de façon asynchrone et CoreWebView2 défini la NewWindow propriété sur le NewWindowRequestedEventArgs, appelez Complete sur l’objet Deferral retourné par la GetDeferral méthode .

Reports en C#

Lors de l’utilisation d’un Deferral en C#, la meilleure pratique consiste à l’utiliser avec un using bloc. Le using bloc garantit que est Deferral terminé même si une exception est levée au milieu du using bloc. Si, au lieu de cela, vous avez du code pour appeler Completeexplicitement , mais qu’une exception est levée avant votre Complete appel, le report n’est terminé qu’un certain temps plus tard, lorsque le garbage collector finit par collecter et supprimer le report. En attendant, WebView2 attend que le code de l’application gère l’événement.

Par exemple, n’effectuez pas les opérations suivantes, car s’il existe une exception avant d’appeler Complete, l’événement WebResourceRequested n’est pas considéré comme « géré » et empêche WebView2 de restituer ce contenu 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();
}

Utilisez plutôt un using bloc, comme dans l’exemple suivant. Le using bloc garantit que le Deferral est terminé, qu’il y ait ou non une 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);
   }
}

Bloquer le thread d’interface utilisateur

WebView2 s’appuie sur la pompe de messages du thread d’interface utilisateur pour exécuter des rappels de gestionnaire d’événements et des rappels de saisie semi-automatique de méthode asynchrone. Si vous utilisez des méthodes qui bloquent la pompe de messages, telles que Task.Result ou WaitForSingleObject, vos gestionnaires d’événements WebView2 et les gestionnaires de saisie semi-automatique de méthode asynchrone ne s’exécutent pas. Par exemple, le code suivant ne se termine pas, car Task.Result arrête la pompe de messages pendant qu’elle attend ExecuteScriptAsync la fin. Étant donné que la pompe de messages est bloquée, le ExecuteScriptAsync n’est pas en mesure de se terminer.

Par exemple, le code suivant ne fonctionne pas, car il utilise Task.Result.

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

Au lieu de cela, utilisez un mécanisme asynchrone await tel que async et await, qui ne bloque pas la pompe de messages ou le thread d’interface utilisateur. Par exemple :

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

Voir aussi