tags:

views:

93

answers:

4

I've been having a bunch of exceptions when trying to use a WebBrowser on a multithread application. COM component, protected memory and other exceptions everywhere I do stuff with the WebBrowser. I just gave up and went back to my single thread version which works fine. I would post code but it's hard to localize the cause of the problem when I get exceptions at so many spots. So, if as a single thread application it runs fine, and if when I run several instances of the same application it also works fine, there should be a way to simulate several applications running from a single application without having to actually make a separated application that I would run from the main application. My question, then, is how can I make Windows treat my threads as if they were different instances? This should eliminate the problem, since, as I said, when they ARE different instances I don't get any exception. Hope I'm being clear enough.

A: 

It sounds like you might be sharing a single WebBrowser instance across threads. If each thread has its own instance, and the threads aren't communicating with each other, I would expect that to be equivalent to running multiple instances of the process.

Zach
+1  A: 

You're talking about different processes inside the same application then.

Jorge Córdoba
+1  A: 

I think your issue may have something to do with the way Microsoft.NET handles UI controls. Basically, any method for a control must be called from the thread that created it (perhaps even the main UI thread exclusively). Otherwise, you will get a bunch of access-related exceptions. I believe you will need to use the InvokeRequired property and Invoke method to call into the control, which also means that you will have to define a delgate function that wraps each method you want to call. Using the WebBroweser.Url property as an example, you could write something like this:

public delegate void SetWebAddressDelegate ( WebBrowser browser, Uri newUrl);

public void SetWebAddress ( WebBrowser browser, Uri newUrl )
{
    if (browser.InvokeRequired)
        browser.Invoke(new SetWebAddressDelegate(SetWebAddress), browser, newUrl);
    else
        browser.Url = newUrl;
}
Javert93
The document at http://msdn.microsoft.com/en-us/library/ms228970.aspx might hint at why the framework requires this. Basically, managed threads are not equivalent to OS threads; they are actually fibers.
Javert93
@Javert93: it said that only for things running in SQL Server, not for .NET threads in general. Even then, they still could be actual OS threads. Certainly on Winforms, they are OS threads and the reason you can't do cross-thread modification of UI controls is the same as the reason you can't do it in regular C: the Win32 controls aren't designed to be threadsafe and there's not generally a good reason to make them so.
siride
@sinde: Actually, if you read the top of the article, it says that although the article is geared towards SQL server, it still applies to any host-based server application. In addition, it is referring to managed threads, not SQL threads. Furthermore, the section that I am citing is towards the bottom (look for "Do Not Assume a Managed Thread Is a Win32 Thread – It Is a Fiber"), which refers to the managed Thread object specifically (even links to the MSDN docs for the class), and MS does not have a "special" version of the .NET Framework just for use by SQL server.
Javert93
+1  A: 

WebBrowser is a COM component under the hood, Internet Explorer. Like many COM components, it requires a 'single threaded apartment'. You have to create one to make it a hospitable home for the component. Basically two essential requirements: the thread needs to be initialized as an STA and it needs to pump a message loop.

Here's one that uses the plumbing provided by Windows Forms:

    private void runBrowserThread(Uri url) {
        var th = new Thread(() => {
            var br = new WebBrowser();
            br.DocumentCompleted += browser_DocumentCompleted;
            br.Navigate(url);
            Application.Run();
        });
        th.SetApartmentState(ApartmentState.STA);
        th.Start();
    }

    void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {
        var br = sender as WebBrowser;
        if (br.Url == e.Url) {
            Console.WriteLine("Natigated to {0}", e.Url);
            Application.ExitThread();
        }
    }

Beware that the DocumentCompleted event gets raised on that worker thread. I arbitrarily used that event to also make the thread terminate.

Hans Passant
I don't get why are you calling `Application.Run()` there?
jsoldi
And what exactly is to pump a message loop?
jsoldi
Application.Run() pumps a message loop. Link: http://stackoverflow.com/questions/2222365/what-is-a-message-pump
Hans Passant