【问题标题】:Reentrant Timer in Windows ServiceWindows 服务中的可重入计时器
【发布时间】:2011-12-10 19:12:33
【问题描述】:

我想构建一个windows服务,它应该在不同的时间执行不同的方法。它根本与准确性无关。 我使用 system.timers.timer,并使用计数器调节要在 Eventhandler 方法中执行的不同方法。到目前为止一切正常。

所有方法都在访问 COM 端口,因此必须一次仅授予一种方法的访问权限。但是由于这些方法可能需要一些时间才能完成,因此计时器可能会再次计时并希望在 COM 端口仍被占用时执行另一个方法。在这种情况下,事件可以而且应该被解除。

简化为一种方法,我的 elapsedEventHandler 方法如下所示(try-catch 和此处排除的不同方法)

注意:虽然它在我的 Win7 x64 上完美运行,但在安装了几乎相同软件的 Win7 x86 机器上,每当要执行的方法需要很长时间时,它都会遇到困难。计时器不会再滴答作响,不会抛出异常。没有!我现在的问题是:我是否在做访问控制和计时器的部分,以便我可以专注于其他事情?我只是不太熟悉计时器,尤其是线程

     private static int m_synchPoint=0;
     private System.Timers.Timer timerForData = null;

    public MyNewService()
    {

        timerForData = new System.Timers.Timer();
        timerForData.Interval = 3000;
        timerForData.Elapsed += new ElapsedEventHandler(Timer_tick);
    }
    //Initialize all the timers, and start them
    protected override void OnStart(string[] args)
    {

        timerForData.AutoReset = true;
        timerForData.Enabled = true;
        timerForData.Start();
    }

    //Event-handled method
    private void Timer_tick(object sender, System.Timers.ElapsedEventArgs e)
    {
            ////safe to perform event - no other thread is running the event?                      
            if (System.Threading.Interlocked.CompareExchange(ref m_synchPoint, 1, 0) == 0)

            {
             //via different else-ifs basically always this is happening here, except switching aMethod,bMethod...
             processedevent++; 
             Thread workerThread = new Thread(aMethod);
             workerThread.Start();
             workerThread.Join(); 
             m_synchPoint=0;
             }
             else
             {
              //Just dismiss the event
              skippedevent++;
             }
     }   

非常感谢您!
非常感谢任何帮助!

【问题讨论】:

  • 我不知道发布的代码如何重现这个问题。
  • 实际上我不能在我的机器上重现这个问题,只能在我正在测试的机器上,我无法在 Visual Studio 中调试。这里的主要问题如下:上面的代码是否正确使用了计时器和比较交换,它保存假设一次只有一个方法在运行? Else:您将如何实现这样的场景?
  • 这不是核心问题,但对于应该是特定于实例的数据,不要使用static(在这种情况下应该是m_synchPoint)。
  • 你为什么打电话给workerThread.Start,紧接着是workerThread.Join?以这种方式做事没有任何好处,因为计时器线程只会等到新线程完成。直接执行aMethod,省去启动新线程的开销。
  • JimMischel:阅读我的 log.debug,在我看来,情况并非如此,这也让我感到惊讶!这有可能吗?我最初的想法是,明确建议服务等待完成...... @bobbymcr:当然是对的,但不是修复=(

标签: c# multithreading windows-services timer interlocked


【解决方案1】:

我建议使用System.Threading.Timer 来实现此功能。您可以在计时器执行时禁用计时器,处理您的数据,然后重新启用计时器。

编辑:

我认为使用System.Threading.Timer 更有意义,因为实际上没有理由需要将计时器放在设计表面上,而这几乎是使用System.Timers.Timer 的唯一原因。我真的希望 MS 无论如何都会删除它,它包裹着 System.Threading.Timer,一开始使用起来并不难。

是的,您确实面临重新进入问题的风险,这就是我指定将超时更改为Timeout.Infinite 的原因。用Timeout.Infinite构造定时器就不会出现这个重入问题。

public class MyClass
{
    private System.Threading.Timer _MyTimer;

public MyClass()
{
    _MyTimer = new Timer(OnElapsed, null, 0, Timeout.Infinite);
}

public void OnElapsed(object state)
{
    _MyTimer.Change(Timeout.Infinite, Timeout.Infinite);
    Console.WriteLine("I'm working");
    _MyTimer.Change(1000, Timeout.Infinite);
}

}

【讨论】:

  • 但您也可以禁用System.Timers.TimerTimer.Enabled = false.
  • 另外,我读到即使在计时器停止之后也可以调用 timerevents?这基本上就是我使用 CompareExchange link 的原因
  • @JimMischel:澄清了我的回答。
  • @Bryan:我也不鼓励使用System.Timers.Timer,因为它会吞下异常,从而隐藏错误。也就是说,包装之所以有用有两个原因:1)它使用事件模型而不是回调。 .NET 程序员通常更熟悉事件模型。其次,如果您在 Windows 窗体应用程序中使用它,将 SynchronizingObject 设置为窗体会导致在 GUI 线程上调用事件处理程序,从而使您不必使用 InvokeRequiredInvoke 来处理访问控制。
  • @Timo:是的,有可能在定时器停止后调用定时器回调(或事件)。但这只有在下一个计时器滴答发生在前一个滴答的回调停止计时器之前才会发生。您需要一个非常快的计时器或非常慢的响应才能发生这种情况。
【解决方案2】:

如果您想跳过方法调用而前一个方法没有完成,只需在调用您的方法之前使用Monitor.TryEnter(lockObject)

编辑: 这是一个例子-

public class OneCallAtATimeClass
{

    private object syncObject;

    public TimerExample()
    {
      syncObject = new object();
    }

    public void CalledFromTimer()
    {    
      if (Monitor.TryEnter(syncObject);)
      {
        try
        {
          InternalImplementation();
        }
        finally
        {
          Monitor.Exit(syncObject);
        }
      }    
    }

    private void InternalImplementation()
    {
      //Do some logic here
    }

  }

【讨论】:

  • 正是我的想法。您可以添加一个示例,显示在方法开头调用 TryEnter,以及在完成时调用 Monitor.Exit 的正确 try...finally
  • @JimMischel - 当然,添加了一个例子
【解决方案3】:

你可以试试这个:

当计时器触发时,禁用计时器。

任务完成后,重新启用计时器...可能在Finally子句中。

【讨论】:

    【解决方案4】:

    正确在进行初始检查时使用CompareExchange 进行测试并设置m_synchPoint 字段。您错误地在方法结束时使用直接赋值将值重置为 0。您应该使用 Interlocked.Exchange 将值重置为 0。作为旁注,您还应该将 m_synchPoint 更改为实例字段——它不应该是静态的。

    【讨论】:

    • 我说“有用”,但我不能 xD
    猜你喜欢
    • 1970-01-01
    • 2012-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-02
    • 1970-01-01
    相关资源
    最近更新 更多