Search code examples
c#.netwinformswebbrowser-control

WebBrowser control: "Specified cast is not valid."


I used a WebBrowser control to navigate to the login page for a WordPress blog. The page loads fine but whenever I try to access the WebBrowser from a thread. I get a specific cast is not valid exception. Also when debugging, everything freezes up for around 5 seconds. When debugging and I try to access the control. I get timed out errors on all of the member variables.

//in constructor of main form
Thread.CurrentThread.ApartmentState = ApartmentState.STA;
this.CheckForIllegalCrossThreadCalls = false;

mainThreadHandle = new Thread(mainThread);
mainThreadHandle.Start();

private void mainThread()
{
    wbMain.Navigate("http://example.com/");

    //navigating is set to false in the document complete event.
    navigating = true;

    while (navigating == true)
        Thread.Sleep(5000);

    try
    {
        //Where I get the issues
        MessageBox.Show(wbMain.DocumentText);
    }
    catch (Exception e)
    {

    }

    Thread.Sleep(1000);
}

Solution

  • WebBrowser is a COM component under the hood. An apartment threaded one, COM takes care of calling its methods in a thread-safe way. Your Navigate() call works for that reason, it is actually executed on the UI thread. What doesn't work is the DocumentText property, it is implemented in the .NET wrapper and they somewhat fumbled the code. It bombs when the COM interop support in the CLR notices that a thread in the MTA tries to access a property of a component that lives on an STA.

    Your call to SetApartmentState() isn't correct. It is made on the wrong thread, the UI thread already is STA. Also the reason it doesn't bomb, you cannot change the apartment state of a thread after it is started. You must call it on the Thread object you created. It still doesn't solve your problem, two STA threads are not compatible.

    Two basic ways to solve your problem. The first one is that you create the WebBrowser object itself on a separate STA thread. The code in this answer shows you how to do that.

    The browser you create that way cannot also be visible on your form. Which is the second way, marshal the call yourself with Control.Invoke(). Doing this is however pretty pointless, all of your code executes on the UI thread anyway, you get no concurrency. There is no free lunch here. Running it on a thread only gives you headaches. If you need time to process the document text then run that code on a separate thread.