C# WebView2在异步任务中的并行使用

C# WebView2在异步任务中的并行使用,c#,multithreading,parallel-processing,semaphore,webview2,C#,Multithreading,Parallel Processing,Semaphore,Webview2,我有一个简单的默认Windows桌面窗体Form1,带有一个按钮btn\u Go作为测试。 我想运行多个并行WebView2实例并处理呈现页面中的html代码。 要并行运行WebView2,我使用SemaphoreSlim(设置为parallel 2)。另一个信号量lim用于等待WebView2呈现文档(有一定的时间延迟) 但是我的代码落在了wait webBrowser.EnsureCoreWebView2Async()上。WebView2实例webBrowser中调试程序的内部异常是: {“

我有一个简单的默认Windows桌面窗体
Form1
,带有一个按钮
btn\u Go
作为测试。
我想运行多个并行WebView2实例并处理呈现页面中的html代码。 要并行运行WebView2,我使用SemaphoreSlim(设置为parallel 2)。另一个信号量lim用于等待WebView2呈现文档(有一定的时间延迟)

但是我的代码落在了wait webBrowser.EnsureCoreWebView2Async()上
WebView2
实例
webBrowser
中调试程序的内部异常是:

{“设置线程模式后无法更改它。(来自HRESULT的异常:0x80010106(RPC_E_CHANGED_mode))“}System.Exception{System.Runtime.InteropServices.COMException}

如何并行多次调用WebView2并处理所有URL

完整演示代码:

使用Microsoft.Web.WebView2.WinForms;
使用制度;
使用System.Collections.Generic;
使用系统线程;
使用System.Threading.Tasks;
使用System.Windows.Forms;
命名空间WebView2Test
{
公共部分类Form1:Form
{
公共表单1(){InitializeComponent();}
私有void btn_Go_Click(对象发送方,事件参数e){Start();}
私有异步void Start()
{
var url=new List(){”https://www.google.com/search?q=test1", "https://www.google.com/search?q=test2", "https://www.google.com/search?q=test3" };
var tasks=新列表();
var-semaphoreSlim=新的信号量lim(2);
foreach(url中的变量url)
{
等待信号量lim.WaitAsync();
任务。添加(
Task.Run(异步()=>{
请尝试{等待StartBrowser(url);}
最后{semaphoreSlim.Release();}
}));
}
等待任务。何时(任务);
}
公共异步任务StartBrowser(字符串url)
{
SemaphoreSlim semaphore=new System.Threading.SemaphoreSlim(0,1);
System.Timers.Timer wait=新的System.Timers.Timer();
等等,间隔=500;
等等,经过的时间+=(s,e)=>
{
semaphore.Release();
};
WebView2 webBrowser=新的WebView2();
webBrowser.NavigationCompleted+=(s,e)=>
{
如果(wait.Enabled){wait.Stop();}
等等,开始();
};
等待webBrowser.ensureCrewebView2Async();
webBrowser.CoreWebView2.Navigate(url);
wait semaphore.WaitAsync();
如果(wait.Enabled){wait.Stop();}
var html=await webBrowser.CoreWebView2.ExecuteScriptAsync(“document.documentElement.outerHTML”);
返回html.Length>10;
}
}
}
我已经安装了

--测试

在主线程中准备
webview 2
,并将其发送到子线程中


我已经尝试从主线程
var bro=new list(){new WebView2(),new WebView2(),new WebView2(),new WebView2()}在
Start
方法中创建
WebView2
列表
并将WebView2实例发送到
wait StartBrowser(bro[index],url)。。。但这会导致相同的错误。

您可以尝试用下面的自定义
TaskRunSTA
方法替换代码中的
任务。运行

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();
    }
}
公共静态任务TaskRunSTA(Func操作)
{
var tcs=新任务完成源(
TaskCreationOptions.RunContinuationsAsynchronously);
变量线程=新线程(()=>
{
Application.Idle+=应用程序\u Idle;
Application.Run();
});
SetApartmentState(ApartmentState.STA);
thread.Start();
返回tcs.Task;
异步无效应用程序\u空闲(对象发送方、事件参数)
{
Application.Idle-=应用程序\u Idle;
尝试
{
等待行动();
tcs.SetResult(空);
}
catch(Exception ex){tcs.SetException(ex);}
Application.ExitThread();
}
}
此方法启动一个新的STA线程,并在此线程内运行一个专用的应用程序消息循环。作为参数传递的异步委托将在此消息循环上运行,并安装相应的同步上下文。只要异步委托不包含任何配置为
.ConfigureAwait(false)
wait
,所有代码,包括由
WebView2
组件引发的事件,都应在此线程上运行


注意:TBH我不知道处理第一次出现的事件是否是在消息循环中嵌入自定义代码的最佳方法,但它似乎工作得很好。值得注意的是,该事件是从内部类
ThreadContext
()附加和分离的,该类每个线程都有一个专用实例。因此,每个线程接收与该线程上运行的消息循环相关联的
Idle
事件。换言之,没有接收来自其他一些不相关的消息循环的事件的风险,这些消息循环在另一个线程上同时运行。

您可能会发现这很有用:
WebView2
组件可能不希望在其生存期内被多个线程访问。感谢您的评论,但这怎么可能呢?我在任务内部创建了
WebView2
,因此,只有此任务才能使用它。我补充说,我还尝试了在主线程中创建所有webview并将一个单独的webview发送到自己的线程中的可能性。上述问题的实现是错误的,但这仍然会在同一行上返回相同的错误。