c# - 在 new Thread() 中创建控件时在正确的线程上调用方法

标签 c# .net multithreading webbrowser-control

我在新的 Thread() 中创建了一个新的 WebBrowser() 控件。

我遇到的问题是,当从 Main Thread 为我的 WebBrowser 调用委托(delegate)时,调用发生在 Main Thread。我希望这发生在 browserThread 上。

private static WebBrowser defaultApiClient = null;

delegate void DocumentNavigator(string url);


private WebApi() {

    // Create a new thread responsible 
    // for making API calls.
    Thread browserThread = new Thread(() => {

        defaultApiClient = new WebBrowser();

        // Setup our delegates
        documentNavigatorDelegate = new DocumentNavigator(defaultApiClient.Navigate);

        // Anonymous event handler
        defaultApiClient.DocumentCompleted += (object sender, WebBrowserDocumentCompletedEventArgs e) => {
            // Do misc. things
        };

        Application.Run();
    });
    browserThread.SetApartmentState(ApartmentState.STA);
    browserThread.Start();

}

DocumentNavigator documentNavigatorDelegate = null;
private void EnsureInitialized() {

    // This always returns "false" for some reason
    if (defaultApiClient.InvokeRequired) {

        // If I jump ahead to this call
        // and put a break point on System.Windows.Forms.dll!System.Windows.Forms.WebBrowser.Navigate(string urlString, string targetFrameName, byte[] postData, string additionalHeaders)
        // I find that my call is being done in the "Main Thread".. I would expect this to be done in "browserThread" instead
        object result = defaultApiClient.Invoke(documentNavigatorDelegate, WebApiUrl);

    }

}

我已经尝试过无数种调用方法的方法:

// Calls on Main Thread (as expected)
defaultApiClient.Navigate(WebApiUrl);

// Calls on Main Thread
defaultApiClient.Invoke(documentNavigatorDelegate, WebApiUrl); 

// Calls on Main Thread
defaultApiClient.BeginInvoke(documentNavigatorDelegate, WebApiUrl); 

// Calls on Main Thread
documentNavigatorDelegate.Invoke(WebApiUrl);

// Calls on random Worker Thread
documentNavigatorDelegate.BeginInvoke(WebApiUrl, new AsyncCallback((IAsyncResult result) => { .... }), null);

更新

让我稍微分解一下我的最终目标以使事情更清楚:我必须使用 WebBrowser.Document.InvokeScript() 进行调用,但是 < strong>Document 直到我调用 WebBrowser.Navigate() 之后才会加载,然后 WebBrowser.DocumentComplete 事件触发。本质上,在 DocumentComplete 触发之前,我无法对 InvokeScript() 进行预期的调用......我想等待文档加载(阻止我的调用者)所以我可以调用 InvokeScript 并以同步方式返回我的结果。

基本上我需要等待我的文档完成,我想这样做的方式是使用 AutoResetEvent() 类,我将在 DocumentComplete 被触发时触发...我需要所有这些事情都发生在一个单独的线程中。

我看到的另一个选择是做一些这样的事情:

private bool initialized = false;
private void EnsureInitialized(){
    defaultApiClient.Navigate(WebApiUrl);
    while(!initialized){
        Thread.Sleep(1000); // This blocks so technically wouldn't work
    }
}

private void defaultApiClient_DocumentComplete(object sender, WebBrowserDocumentCompletedEventArgs e){
    initialized = true;
}

最佳答案

这是设计使然。控件的 InvokeRequired/BeginInvoke/Invoke 成员需要创建控件的 Handle 属性。这是它可以确定调用哪个特定线程的主要方式。

但这并没有发生在您的代码中,句柄通常仅在您将控件添加到父级的控件集合并且父级使用 Show() 显示时创建。也就是说,实际上是为浏览器创建了宿主窗口。这些都没有发生在您的代码中,因此 Handle 仍然是 IntPtr.Zero 并且 InvokeRequired 返回 false

这实际上不是问题。 WebBrowser 类很特殊,它是一个底层的 COM 服务器。 COM 自己处理线程细节,而不是将其留给程序员,这与 .NET 的工作方式非常不同。并且它会自动编码对其 Navigate() 方法的调用。这是完全自动的,不需要任何帮助。只需要一个 COM 服务器的好客之家,您通过创建一个 STA 线程并使用 Application.Run() 泵送一个消息循环来创建一个。它是 COM 用于执行自动编码(marshal)处理的消息循环。

因此您只需在主线程上调用 Navigate() 就不会出错。 DocumentCompleted 事件仍会在辅助线程上触发,您可以在该线程上愉快地修改 Document。

不确定为什么这是个问题,它应该一切正常。也许您只是对其行为感到困惑。如果不是那么this answer可以帮助您提供更通用的解决方案。顺便说一句,不要太害怕反对者,在工作线程上显示 UI 充满了陷阱,但您永远不会在这里实际显示任何 UI 也永远不会创建窗口。

关于c# - 在 new Thread() 中创建控件时在正确的线程上调用方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21790151/

相关文章:

c# - asp.net core 2.2升级并且类库中不提供HttpSessionState

c# - 如何让 XML 注释出现在不同的项目 (dll) 中?

c# - 在外部参数更改时通知更改

c++ - 如何声明一个函数,它是一个类的成员并返回一个指向线程的函数指针?

c# - 如何使用 System.Timers.Timer 访问/修改另一个线程上的控件?

java - 如何并发运行ScheduledThreadPoolExecutor

c# - 是否可以通过 URL 调用 Web API 获取实时数据?

c# - 删除文件夹 C# .net Core

c# - 在不调用构造函数的情况下将对象存储在字典中

c# - 生成操作 : Content - how do get it to the Executing Assemblies folder instead of just the project folder?