Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
681 views
in Technique[技术] by (71.8m points)

c# - Calling method on correct thread when control is created in new Thread()

I've created a new WebBrowser() control in a new Thread().

The problem I'm having, is that when invoking a delegate for my WebBrowser from the Main Thread, the call is occurring on the Main Thread. I would expect this to happen on 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);

    }

}

I've tried invoking the method a myriad of ways:

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

Update

Let me break down my end-goal a little bit to make things more clear: I have to make calls using WebBrowser.Document.InvokeScript(), however Document is not loaded until after I call WebBrowser.Navigate() and THEN the WebBrowser.DocumentComplete event fires. Essentially, I cannot make my intended call to InvokeScript() until after DocumentComplete fires... I would like to WAIT for the document to load (blocking my caller) so I can call InvokeScript and return my result in a synchronous fashion.

Basically I need to wait for my document to complete and the way I would like to do that is with a AutoResetEvent() class which I will trigger upon DocumentComplete being fired... and I need all this stuff to happen in a separate thread.

The other option I see is doing something like this:

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;
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This is by design. The InvokeRequired/BeginInvoke/Invoke members of a control require the Handle property of the control to be created. That is the primary way by which it can figure out to what specific thread to invoke to.

But that did not happen in your code, the Handle is normally only created when you add a control to a parent's Controls collection and the parent was displayed with Show(). In other words, actually created the host window for the browser. None of this happened in your code so Handle is still IntPtr.Zero and InvokeRequired returns false.

This is not actually a problem. The WebBrowser class is special, it is a COM server under the hood. COM handles threading details itself instead of leaving it up to the programmer, very different from the way .NET works. And it will automatically marshal a call to its Navigate() method. This is entirely automatic and doesn't require any help. A hospitable home for the COM server is all that's needed, you made one by creating an STA thread and pumping a message loop with Application.Run(). It is the message loop that COM uses to do the automatic marshaling.

So you can simply call Navigate() on your main thread and nothing goes wrong. The DocumentCompleted event still fires on the helper thread and you can take your merry time tinkering with the Document on that thread.

Not sure why any of this is a problem, it should work all just fine. Maybe you were just mystified about its behavior. If not then this answer could help you with a more universal solution. Don't fear the nay-sayers too much btw, displaying UI on a worker thread is filled with traps but you never actually display any UI here and never create a window.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...