【问题标题】:Best practice for continual running process in C#C#中持续运行进程的最佳实践
【发布时间】:2011-04-17 22:06:36
【问题描述】:

我正在使用 .NET 框架 3.5 版在 C#.NET 中开发一个项目。

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

我的第一次尝试是使用每 500 毫秒滴答一次的时间,然后调用一个后台工作人员,该工作人员轮询一个位置,然后返回一个温度。如果后台工作人员忙,则计时器计时时,它会返回并在 500 毫秒后再次尝试。有人建议我一起摆脱后台工作人员,只在计时器滴答事件中进行民意调查。因此,我将计时器的 AutoReset 属性设置为 false,然后在每次轮询完成时重新启动计时器。这两种技术在我的应用程序中的行为方式似乎完全相同,所以我不确定一种是否比另一种更好。每次我想使用新的 ThreadStart 和所有这些进行轮询操作时,我也尝试创建一个新线程。这似乎也很好用。

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

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

任何意见、建议、cmets 将不胜感激。

谢谢,乔丹

【问题讨论】:

    标签: c# .net multithreading com


    【解决方案1】:

    请记住,计时器隐藏了它自己的后台工作线程,它基本上会休眠一段时间,然后触发它的 Elapsed 事件。知道了这一点,将轮询放入 Elapsed 是有意义的。这将是 IMO 的最佳实践,而不是从线程启动线程。您也可以启动和停止定时器,因此切换模式的代码可以停止()定时器,执行任务,然后再次启动()它,定时器甚至不必知道望远镜 IsBusy。

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

    【讨论】:

      【解决方案2】:

      所以看起来您已经查看了 2 个选项:

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

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

      如果获取值的速度足够快,为简单起见,请坚持 #1。

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

      【讨论】:

      • 如果您需要更多控制,也可以将仅使用 BackgroundThread 的基本思想用于常规线程。
      【解决方案3】:

      联系望远镜并获取当前值的过程实际上是否需要足够长的时间来保证轮询?您是否尝试过在获得当前值时放弃多线程并只是阻塞?

      但是,要回答您的问题,我建议不要使用后台工作程序,而是使用持续更新属性的实际线程。

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

      我建议使用真正的专用线程而不是线程池,因为我不知道在混合后台线程和 COM 服务器时可能会发生什么。此外,公寓状态可能会发挥作用;使用 Thread 可以尝试 STA,但不能使用线程池线程。

      【讨论】:

        【解决方案4】:

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

        即您单击一个按钮,它会调用一个函数,该函数会访问 COM 对象获取数据,然后更新 UI。全部采用阻塞、同步的方式。这将确切地告诉您是多线程代码让您僵住了,还是 COM 交互本身。

        【讨论】:

        • 我喜欢这个主意!我试试看
        【解决方案5】:

        使用 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); 
          } 
        }
        

        【讨论】:

        • bool bRet = ThreadPool.QueueUserWorkItem(new WaitCallback(ReLoadThreadFunc));
        • 私有 void ThreadFunc(object objState) { bool bMessageAvailable = false;对象消息=空;字符串 strLabel = string.Empty;诠释 iCurChunk = 0; int iTotalChunk = 0; int iCount = 0;结果 ret; // 进入循环 bContinue = true; while (bContinue) { // 做事 // 睡眠 Thread.Sleep(m_iWaitTime_ms); } }
        • @bigtang - 您可以将此代码编辑到您的帖子中并使用“101010”图标对其进行格式化。
        • @Steve - 我很难输入伪代码 - 浏览器实际上崩溃了几次 - 我是新来的 - 101010 图标是什么意思?
        • @Steve - 当你回答问题时,我会看到 101010 代码按钮 - 谢谢你 - 我也可以在评论中这样做(像这样)吗?
        猜你喜欢
        • 2012-01-07
        • 1970-01-01
        • 2018-11-22
        • 2012-02-24
        • 2020-02-06
        • 1970-01-01
        • 1970-01-01
        • 2010-09-29
        • 2012-01-10
        相关资源
        最近更新 更多