C#中持续运行过程的最佳实践

时间:2010-09-27 18:13:15

标签: c# .net multithreading com

我正在使用.NET framework 3.5版在C#.NET上开展一个项目。

我的项目有一个名为Focuser.cs的类,它代表一个物理设备,一个望远镜聚焦器,可以通过串行(RS-232)端口与PC通信。我的类(Focuser)具有CurrentPosition,CurrentTemperature等属性,它们代表聚焦器的当前条件,它可以随时改变。因此,我的Focuser类需要不断轮询设备以获取这些值并更新其内部字段。我的问题是,执行此连续轮询序列的最佳方法是什么?有时,用户需要将设备切换到不同的模式,这需要能够停止轮询,执行某些操作,然后恢复轮询。

我的第一次尝试是使用每500ms滴答一次的时间,然后调出一个背景工作者,该工作人员轮询一个位置,然后返回一个温度。当计时器勾选后台工作人员是忙碌时,它只会返回并在500毫秒后再次尝试。有人建议我一起摆脱背景工作者,只是在计时器刻度事件中进行轮询。因此,我将计时器的AutoReset属性设置为false,然后每次轮询完成时重新启动计时器。这两种技术似乎在我的应用程序中表现完全相同,所以我不确定一个是否优于另一个。每次我想使用新的ThreadStart进行轮询操作时,我也尝试创建一个新线程。这似乎也很好。

我应该提一件事。此类是COM对象服务器的一部分,这基本上意味着将通过COM调用生成的类库。我不确定这是否对答案有任何影响,但我只是认为我应该把它扔出去。

我问这一切的原因是我的所有测试工具运行和调试版本工作正常但是当我进行发布版本并尝试从另一个应用程序调用我的类时,该应用程序冻结了我我很难确定原因。

任何建议,建议和意见都将不胜感激。

谢谢,乔丹

5 个答案:

答案 0 :(得分:2)

请记住,计时器隐藏了自己的后台工作线程,该线程基本上会休息一段时间,然后触发其经过的Elapsed事件。知道这一点,将投票放在Elapsed中是有道理的。这将是IMO的最佳实践,而不是从线程启动线程。你也可以启动和停止定时器,所以切换模式的代码可以停止()定时器,执行任务,然后再启动()它,定时器甚至不必知道望远镜IsBusy。

但是,我要跟踪的是Elapsed事件处理程序的另一个实例是否仍在运行。你可以锁定Elapsed处理程序的代码,或者你可以设置一个从任何线程可见的标志,指示另一个Elapsed()事件处理程序仍在工作;看到此标志集的Elapsed事件处理程序可以立即退出,避免使用串行端口的并发问题。

答案 1 :(得分:2)

所以看起来你看了两个选项:

  1. 定时器。 Timer在等待时使用非阻塞(使用另一个线程),因此程序的其余部分可以继续运行并具有响应性。当计时器事件开始时,您只需获取/更新当前值。

  2. Timer + BackgroundWorker。后台工作者也只是一个单独的线程。实际启动线程可能需要更长时间而不是简单地获取当前值。除非需要很长时间才能获得当前值并导致程序无响应,否则这是不必要的复杂性。

  3. 如果获得的值足够快,请坚持#1以简化。

    如果获取值很慢,#2将起作用但不必要的线程启动一个线程。相反,只使用BackgroundWorker(无计时器)。创建BackgroundWorker一次并存储在变量中。无需每次都重新创建它。确保将WorkerSupportsCancellation设置为true。每当您想要开始检查值时,在主程序线程上执行bgWorker.RunWorkerAsync()。如果要停止,请执行bgWorker.CancelAsync()。在DoWork方法中,有一个循环检查值并执行Thread.Sleep(500)。由于它是一个单独的线程,它不会使您的程序无响应。在循环条件中,还要检查轮询是否已取消并中断。您可能需要一种方法将值返回到主线程。如果整数足够好,您可以使用ReportProgress()。否则,您可以创建一个对象来保存内容,但在阅读和修改之前请确保lock (object) { }。这是一个快速摘要,但如果你走这条路,我建议你阅读:http://www.albahari.com/threading/part3.aspx#_BackgroundWorker

答案 2 :(得分:1)

接触望远镜和获取当前值的过程实际上需要足够长的时间才能进行轮询吗?您是否尝试删除多线程并在获得当前值时阻塞?

但是,为了回答你的问题,我建议不要使用后台工作程序,而应该使用实际的Thread来持续更新属性。

如果所有这些属性都是只读的(你可以设置望远镜的温度吗?)并且它们之间没有依赖关系(例如,一次更新多个属性不需要任何事务)你可以删除所有阻塞代码和当其他线程访问属性时,让你的线程不知不觉地更新。

我建议使用真正的专用线程而不是线程池,因为缺乏对混合后台线程和COM服务器时可能发生的事情的了解。此外,公寓国家可能会发挥作用;使用线程,你可以尝试STA,但你不能用线程池线程。

答案 3 :(得分:1)

你说应用程序在发布版本中冻结了吗? 为了消除额外的变量,我将所有定时器/多线程代码从应用程序中取出(只需将其注释掉),并使用简单的阻塞方法进行尝试。

即。您单击一个按钮,它调用一个函数,该函数命中COM对象的数据,然后更新UI。全部采用阻塞,同步的方式。这将告诉您确定这是多线程代码是否会让您失望,或者它是否是COM交互本身。

答案 4 :(得分:0)

如何使用ThreadPool启动后台线程?然后输入一个基于bool(While(bContinue))的循环,循环并完成你的工作,然后循环结束时的Thread.Sleep - 退出程序将包括将bContinue设置为false以便线程停止 - 也许挂钩它直到Windows服务中的OnStop事件

bool bRet = ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadFunc));
private void ThreadFunc(object objState) 
{ 
  // enter loop 
  bContinue = true; 
  while (bContinue) { 
     // do stuff 
     // sleep 
     Thread.Sleep(m_iWaitTime_ms); 
  } 
}