I have simple default Windows desktop form Form1
with one button btn_Go
as a test.
I want to run multiple parallel WebView2 instances and process html code from rendered page.
To run WebView2 in parallel I use SemaphoreSlim (set to parallel 2). Another SemaphoreSlim is used for wait until WebView2 render the document (with some time delay).
But my code falls on await webBrowser.EnsureCoreWebView2Async();
. Inner exception from debuger in WebView2
instance webBrowser
is:
{"Cannot change thread mode after it is set. (Exception from HRESULT: 0x80010106 (RPC_E_CHANGED_MODE))"} System.Exception {System.Runtime.InteropServices.COMException}
How can I call WebView2 multiple times in parallel and process all urls?
Full demo code:
using Microsoft.Web.WebView2.WinForms;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WebView2Test
{
public partial class Form1 : Form
{
public Form1() { InitializeComponent(); }
private void btn_Go_Click(object sender, EventArgs e) { Start(); }
private async void Start()
{
var urls = new List<string>() { "https://www.google.com/search?q=test1", "https://www.google.com/search?q=test2", "https://www.google.com/search?q=test3" };
var tasks = new List<Task>();
var semaphoreSlim = new SemaphoreSlim(2);
foreach (var url in urls)
{
await semaphoreSlim.WaitAsync();
tasks.Add(
Task.Run(async () => {
try { await StartBrowser(url); }
finally { semaphoreSlim.Release(); }
}));
}
await Task.WhenAll(tasks);
}
public async Task<bool> StartBrowser(string url)
{
SemaphoreSlim semaphore = new System.Threading.SemaphoreSlim(0, 1);
System.Timers.Timer wait = new System.Timers.Timer();
wait.Interval = 500;
wait.Elapsed += (s, e) =>
{
semaphore.Release();
};
WebView2 webBrowser = new WebView2();
webBrowser.NavigationCompleted += (s, e) =>
{
if (wait.Enabled) { wait.Stop(); }
wait.Start();
};
await webBrowser.EnsureCoreWebView2Async();
webBrowser.CoreWebView2.Navigate(url);
await semaphore.WaitAsync();
if (wait.Enabled) { wait.Stop(); }
var html = await webBrowser.CoreWebView2.ExecuteScriptAsync("document.documentElement.outerHTML");
return html.Length > 10;
}
}
}
I have WebView2 runtime installed.
-- Tests
Prepare WebView2
in main thread and send it into child threads.
I have already tried to create list of WebView2
in Start
method from main Thread var bro = new List<WebView2>() { new WebView2(), new WebView2(), new WebView2() };
and send WebView2 instance into await StartBrowser(bro[index], url);
... but this ends with the same error.
You could try replacing the Task.Run
in your code with the custom TaskRunSTA
method below:
public static Task TaskRunSTA(Func<Task> action)
{
var tcs = new TaskCompletionSource<object>(
TaskCreationOptions.RunContinuationsAsynchronously);
var thread = new Thread(() =>
{
Application.Idle += Application_Idle;
Application.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
return tcs.Task;
async void Application_Idle(object sender, EventArgs e)
{
Application.Idle -= Application_Idle;
try
{
await action();
tcs.SetResult(null);
}
catch (Exception ex) { tcs.SetException(ex); }
Application.ExitThread();
}
}
This method starts a new STA thread, and runs a dedicated application message loop inside this thread. The asynchronous delegate that you pass as argument will run on this message loop, with the appropriate synchronization context installed. As long as your asynchronous delegate does not contain any await
configured with .ConfigureAwait(false)
, all your code, including the events raised by the WebView2
component, should run on this thread.
Note: TBH I don't know if handling the first occurrence of the Application.Idle
event is the best way to embed custom code in a message loop, but it seems to work quite well. It is worth noting that this event is attached and detached from an internal class ThreadContext
(source code), and this class has a dedicated instance per thread. So each thread receives the Idle
events that are associated with the message loop running on that thread. In other words there is no risk of receiving an event that is originated from some other unrelated message loop, that runs concurrently on another thread.
User contributions licensed under CC BY-SA 3.0