3

I'm trying to access information on a web browser from another thread. When trying to access the browser.DocumentTitle, I get this error:

The name DocumentTitle does not exist in the current context

I can successfully navigate to webpages inside the DoWork or ProcessWebPage methods but I cannot access the GetTitle function without crashing. I have been working on this part alone for days and simply cannot figure it out.

Here is the problem code:

BROWSER CODE

class BrowserInterface : Form
{
    WebBrowser browser;
    Thread thread;

    State state;

    public State State { get { return state; } }

    public BrowserInterface()
    {
        Initialize();
    }

    void Initialize()
    {
        browser = new WebBrowser();
        state = State.Null;
        state = State.Initializing;
        thread = new Thread(StartThread);
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();

        while (state == State.Initializing) Thread.Sleep(20);
    }

    void StartThread()
    {
        browser = new WebBrowser();
        browser.Dock = DockStyle.Fill;
        browser.Name = "webBrowser";
        browser.ScrollBarsEnabled = false;
        browser.TabIndex = 0;
        browser.DocumentCompleted +=
            new WebBrowserDocumentCompletedEventHandler(this.Web_Completed);
        Form form = new Form();
        form.Controls.Add(browser);
        form.Name = "Browser";
        state = State.Null;
        Application.Run(form);
    }

    public void Navigate(string url)
    {
        state = State.Navigating;
        if (browser.IsDisposed)
            Initialize();
        browser.Navigate(url);
    }

    public string GetTitle()
    {
        if (InvokeRequired)
        {
            BeginInvoke(new MethodInvoker(() => GetTitle()));
        }
        return browser.DocumentTitle;
    }

    private void Web_Completed(object sender, WebBrowserDocumentCompletedEventArgs e)
    {
        var br = sender as WebBrowser;
        if (br.Url == e.Url)
            state = State.Completed;

    }
}

enum State
{
    Initializing,
    Null,
    Navigating,
    Completed
}

OTHER THREAD

class Controller
{
    public int ThreadsAllowed;

    private ManualResetEvent[] resetEvent;
    private BrowserInterface[] browser;

    static Thread mainThread;

    bool run;
    bool exit;

    public Controller(int threadsAllowed)
    {
        ThreadsAllowed = threadsAllowed;

        resetEvent = new ManualResetEvent[ThreadsAllowed];
        browser = new BrowserInterface[ThreadsAllowed];

        for (int i = 0; i < ThreadsAllowed; i++)
        {
            resetEvent[i] = new ManualResetEvent(true);
            browser[i] = new BrowserInterface();
        }

        ThreadPool.SetMaxThreads(ThreadsAllowed, ThreadsAllowed);

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

        run = false;
        exit = false;
    }

    public void Run()
    {
        run = true;
    }

    void RunThread()
    {
        while (true)
        {
            while (!run) Thread.Sleep(20);
            while (mode == ScoutMode.Off) Thread.Sleep(100);

            //wait for the last set to complete
            WaitHandle.WaitAll(resetEvent);
            if (exit)
                break;

             for (int i = 0; i < ThreadsAllowed; i++)
             ThreadPool.QueueUserWorkItem(DoWork, i);             
        }
    }

    void DoWork(object o)
    {
        int i = (int)o;
        if(browser[i].state == State.null)
        {
            …
            … navigation code that works …
            …
            return;
        }
        else if(browser[i].state == State.Completed)    
            ProcessWebPage(i);         

    }

    void ProcessWebPage(int i)
    {
        string title;
        try
        {
            title = browser[i].GetTitle();
        }
        catch { return; }
    }
}
user1642357
  • 53
  • 1
  • 1
  • 6
  • As you see, controlling WebBrowser component in a different thread is very problematic. Can't you use [HtmlAgilityPack](http://htmlagilitypack.codeplex.com/)? – L.B Sep 02 '12 at 21:02
  • Can you provide more details about the error you're getting? Is it an exception (which one, more detailed), a message box or something else? – Nikola Malešević Sep 02 '12 at 21:42
  • I placed breakpoints inside the Invoke portion of the code, Doesnt even go through there. It returns straight from `browser.DocumentTitle` even though it doesnt exist in the current context, causing the exception in the catch statment `System.InvalidCastException {"Specified cast is not valid."}` – user1642357 Sep 02 '12 at 21:53
  • If I set another field inside the BrowserInterface `string title` and set it in the documentCompleted method, and finaly return the `title` in GetTitle instead of browser.DocumentComplete, it works perfectly. – user1642357 Sep 02 '12 at 22:32
  • Sorry, I can't help you further. You should definitely investigate why browser.DocumentTitle is out of current context because the problem is there. Only after you resolve that, can you move on to invoking and other issues. Perhaps you should post **the whole exception information** and post it as a part of the question, others might find it useful to help you. – Nikola Malešević Sep 02 '12 at 22:34
  • Well, there's a way to do it. Now you should not be needing invoking at all. – Nikola Malešević Sep 02 '12 at 22:35

2 Answers2

1

What hurts my eye is your GetTitle function. When using MethodInvoker, you're dealing with methods of void type, that is, you cannot get return value from the function. That's why you need a delegate which will return you the value.

Also, you have to have else statement, so to not try to return the value when invoking is in fact required.

class BrowserInterface : Form
{
    /* ... */

    private delegate string StringDelegate();

    public string GetTitle()
    {
        /*
        if (InvokeRequired)
        {
            BeginInvoke(new MethodInvoker(() => GetTitle()));
        }
        return browser.DocumentTitle;
        */

        if (InvokeRequired)
        {
            object result = Invoke(new StringDelegate(GetTitle));
            return (string)result;
        }
        else
            return browser.DocumentTitle;
    }

    /* ... */
}
Nikola Malešević
  • 1,738
  • 6
  • 21
  • 44
0

At first, use browsers invoke instead of forms one. And the main problem that after invokation you will return to code and try to access browser.DocumentTitle as background thread. To avoid this, add else construction.

public string GetTitle()
{
    if (this.browser.InvokeRequired)
    {
        this.browser.Invoke(new MethodInvoker(() => GetTitle()));
    }
    else
    {
        return browser.DocumentTitle;
    }
}