2012-01-31 43 views
4

我是一个不是很先进的学习示例C#编码器,这就是为什么无论互联网上的信息量如何,这个问题完全困扰我。定时器和线程的问题

我本质上是创建一个程序,在计时器上反复轮询网站以获取一些信息。在此过程中,将创建一个WebBrowser控件以导航到信息(需要进行身份验证)。程序在启动时运行这一系列事件,然后使用一个设置为每10分钟(当然,调试时减少)的System.Timers.Timer来做同样系列的事件,但是当我的Timer.Elapsed事件触发该过程时,我得到一个:

ThreadStateException与说明书ActiveX控件“{8856F961-340A-11D0-A96B-00c04fd705a2”不能被实例化,因为当前线程不是在一个单一的线程单元。

这是我的程序的瘦身版本。

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... 

     } 

我想我在那里有足够的代码来绘制图片。正如你所看到的,当它创建另一个WebBrowser时,它会抛出错误。

我真的很难过,我只是开始刮在线程上的表面,这可能是我为什么如此难倒。

//我的解决方案/ 我最终将WebBrowser的创建移出了方法,并使其静态重用WebBrowser控件。我也将我的System.Timers.Timer换成System.Threading.Timer。似乎解决了这个问题。

+1

这是因为WebBrowser控件是不允许在自己的线程。你可以使用WebClient吗?否则有一个[STAThread]属性的方法..不知道这是否工作.. – f2lollpll 2012-01-31 07:49:10

+1

我不知道WebBrowser控件,但通常控件只能在窗体运行的线程中使用。您可以切换到一个系统。 Windows.Forms.Timer(在这种情况下你不会是多线程的),或者更难以使用WebRequest(需要一些工作才能获得授权) – 2012-01-31 07:49:26

回答

7

MSDN documentation for WebBrowser指出:

web浏览器类只能在设置为单线程单元(STA)模式的线程中使用。要使用此类,请确保您的Main方法是,标记为[STAThread]属性

此外,如果要定期与UI控件进行交互,请将System.Timers.Timer更改为System.Windows.Forms.Timer。或者,将System.Timers.TimerSynchronizingObject属性设置为父控件,以强制您的计时器在右侧线程上调用调用。所有的WinForms控件只能从相同的,唯一的UI线程访问。

.NET的BCL中有三种类型的定时器,它们每一种的表现都非常不同。查看此MSDN文章以进行比较:Comparing the Timer Classes in the .NET Framework Class Library (web archive)或此brief comparison table

+0

我试图将auto_refresh计时器交换到Windows.Form。计时器,但它不会触发.Tick事件,我不知道为什么。文档没有给我任何提示。 – Josh 2012-01-31 20:19:59

+0

@Josh:这很奇怪。你确定你开始了吗?并且'Interval'值是合理的?你能发布你的代码吗? – Groo 2012-01-31 22:40:29

+0

[STAThread]属性实际上并没有工作,但正如你所建议的那样,我交换了一个不同的定时器,这有助于 - 以及一些其他的代码修改。间隔值为30秒左右用于测试。我最终与Threading.Timer一起工作,这对我的意大利面条代码来说工作得很好。 – Josh 2012-02-02 08:12:48

3

我会推荐使用WebClient类而不是WebBrowser。此外,将已创建的实例作为私有属性存储似乎更好,而不是每次需要轮询Web站点时都创建新实例。

如下:

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... 

     } 
+0

+1是的,实际上这是正确的方式。创建一个不会与用户交互的控件是错误的。 – Groo 2012-01-31 08:01:09

+0

选择使用WebBrowser是因为我花了大量时间试图让WebClient工作,但无法使认证过程成功。 WebClient是我的第一选择。 – Josh 2012-01-31 08:08:28

+0

对于身份验证,您可以传递[NetworkCredential](http://msdn.microsoft.com/en-us/library/system.net.networkcredential.aspx)类的实例。对于我来说,指定登录名,密码和域名就足够了,如下面的'新的NetworkCredential(“userId”,“password”,“domainName”);'。 – 2012-01-31 08:23:35

0

正如Groo说,你应该使用System.Windows.Forms的。定时器,或者如果你真的想这样做你运行在另一个线程,您应该使用Invoke方法做任何UI相关的东西:

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(); 
}