Josh
Josh

Reputation: 557

Trouble with timers and threads

I'm a learn-by-example C# coder who isn't very advanced, which is why this problem is completely stumping me regardless of the amount of information on the internet.

I'm essentially creating a program that is, on a timer, repeatedly polling a website to get some information. During this process, a WebBrowser control is created to navigate to the information (needed for authentication). The program runs this series of events at startup, then using a System.Timers.Timer set to every 10 minutes (less for debugging of course) to do that same series of events yet when my Timer.Elapsed event triggers that process, I get a:

ThreadStateException with the description as ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be instantiated because the current thread is not in a single-threaded apartment.

Here is a slimmed down version of my program.

private void Form1_Load(object sender, EventArgs e)
        {
            GetDataFromWebBrowser();
            Set_Auto_Refresh_Timer();
        }

private void Set_Auto_Refresh_Timer()
        {
            System.Timers.Timer TimerRefresh = new System.Timers.Timer(10000);
            TimerRefresh.Elapsed += new System.Timers.ElapsedEventHandler(TimerRefresh_Elapsed);
            TimerRefresh.AutoReset = true;
            TimerRefresh.Start();
        }    

private void TimerRefresh_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            GetDataFromWebBrowser();
        }

private void GetDataFromWebBrowser()
        {
            WebBrowser wb = new WebBrowser();  <--This is where the error is thrown.

            ...get web data...

        }

I think I got enough code in there to paint the picture. As you can see, when it gets to creating another WebBrowser, it throws the error.

I'm really stumped and I'm just starting to scrape the surface on Threading which is probably why I'm so stumped.

//Solution for me/ I ended up moving the WebBrowser creation out of the method as well as making it static to just reuse the WebBrowser control. I also swapped my System.Timers.Timer to System.Threading.Timer. Seemed to fix the problem.

Upvotes: 4

Views: 2227

Answers (3)

vgru
vgru

Reputation: 51224

The MSDN documentation for WebBrowser states that:

The WebBrowser class can only be used in threads set to single thread apartment (STA) mode. To use this class, ensure that your Main method is marked with the [STAThread] attribute.

Also, change your System.Timers.Timer to a System.Windows.Forms.Timer if you want to interact with UI controls in regular intervals. Alternatively, set the SynchronizingObject property of your System.Timers.Timer to a parent control to force your timer to invoke calls on the right thread. All WinForms controls can only be accessed from the same, one and only UI thread.

There are three types of timers in .NET's BCL, each of them acting very differently. Check this MSDN article for a comparison: Comparing the Timer Classes in the .NET Framework Class Library (web archive) or this brief comparison table.

Upvotes: 7

Moha Dehghan
Moha Dehghan

Reputation: 18443

As Groo said, you should use System.Windows.Forms.Timer, or if you really want to do you operation in another thread, you should use the Invoke method to do any UI related stuff:

private void GetWebData()
{
     ...get web data...
}

private void ShowWebData()
{
    WebBrowser wb = new WebBrowser();
    // other UI stuff
}

private void TimerRefresh_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    GetDataFromWebBrowser();
}

private void GetDataFromWebBrowser()
{
    GetWebData();

    if (this.InvokeRequired)
        this.Invoke(new Action(ShowWebData));
    else
        ShowWebData();
}

Upvotes: 0

Andrii Kalytiiuk
Andrii Kalytiiuk

Reputation: 1497

I would recommend using WebClient class instead of WebBrowser. Also it seems to be better to store already created instance as a private property instead of creating new instance each time you need to poll a web site.

As following:

private WebClient webClient;
private void Form1_Load(object sender, EventArgs e)
        {
            GetDataFromWebBrowser();
            Set_Auto_Refresh_Timer();
            this.webClient = new WebClient();
        }

private void Set_Auto_Refresh_Timer()
        {
            System.Timers.Timer.TimerRefresh = new System.Timers.Timer(10000);
            TimerRefresh.Elapsed += new System.Timers.ElapsedEventHandler(TimerRefresh_Elapsed);
            TimerRefresh.AutoReset = true;
            TimerRefresh.Start();
        }    

private void Set_Auto_Refresh_Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            GetDataFromWebBrowser();
        }

private void GetDataFromWebBrowser()
        {
            ...perform required work with webClient...
            ...get web data...

        }

Upvotes: 3

Related Questions