Windows服务中的可重入计时器

4
我想构建一个Windows服务,它应该在不同的时间执行不同的方法。这与准确性无关。 我使用System.Timers.Timer,在事件处理程序方法中使用计数器来调节要执行的不同方法。到目前为止,这样做得很好。 所有的方法都访问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++;
             }
     }   

非常感谢您的帮助!
任何帮助都将不胜感激!

我不知道发布的代码如何能够重现这个问题。 - Hans Passant
实际上,我在我的机器上无法重现这个问题,只有在我测试的机器上才能出现,而我无法在Visual Studio中进行调试。这里的主要问题是:上面的代码是否正确使用了计时器和比较交换,使我们可以安全地假设一次只有一个方法在运行?否则:你会如何实现这种情况? - Timo Jak
这不是核心问题,但是不要对应该是实例特定的数据使用“static”(如在这种情况下应该使用“m_synchPoint”)。 - bobbymcr
为什么你要立即调用 workerThread.Start 然后紧接着调用 workerThread.Join?这样做没有任何好处,因为计时器线程只会等待新线程完成。直接执行 aMethod,避免额外开启一个线程的开销。 - Jim Mischel
我可能没有表达清楚...我确实理解你所说的。我的意思是,当我按照你的建议操作时,从我的调试文件中发现Timer_tick不会等待aMethod完成后再结束自己。这真的可能吗?这就是我一开始在第一个线程中加入新线程的原因...为了能够明确地说“等待”。 - Timo Jak
显示剩余3条评论
4个回答

4
我建议使用System.Threading.Timer来实现此功能。您可以在定时器执行时禁用它,处理您的数据,然后重新启用定时器。
编辑:
我认为更有意义的是使用System.Threading.Timer,因为您没有必要将计时器放在设计界面上,这几乎是使用System.Timers.Timer的唯一原因。不管怎样,我真的希望微软能够将其删除,因为它包装了并没有那么难使用的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 - Jim Mischel
此外,我了解到即使在计时器停止后,时间事件仍然可以被调用。这基本上是我使用CompareExchange的原因[链接](http://msdn.microsoft.com/en-us/library/system.timers.timer.stop.aspx)。 - Timo Jak
@JimMischel:我稍微澄清了一下我的回答。 - Bryan Crosby
1
@Bryan:我也不鼓励使用System.Timers.Timer,因为它会吞掉异常,从而隐藏错误。尽管如此,包装器有两个用处:1)它使用事件模型而不是回调。事件模型对于.NET程序员来说更加熟悉。其次,如果您在Windows Forms应用程序中使用它,将SynchronizingObject设置为表单会导致事件处理程序在GUI线程上被调用,使您无需使用InvokeRequiredInvoke来访问控件。 - Jim Mischel
@Timo:是的,定时器回调(或事件)有可能在定时器停止后被调用。但只有当下一个定时器滴答发生在上一个滴答的回调停止定时器之前才会发生这种情况。你需要一个非常快的定时器或者一个非常慢的响应才能发生这种情况。 - Jim Mischel
显示剩余2条评论

2
你可以尝试这样做:
当定时器触发时,禁用定时器。
当任务完成时,在 Finally 子句中重新启用定时器。

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

我想说“有用”,但我不行 xD - Timo Jak

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,以及正确的try...finally在完成时调用Monitor.Exit - Jim Mischel
@JimMischel - 当然,已经添加了一个例子。 - Maxim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接