使用带有Threading.Timer的锁

时间:2009-11-08 23:48:26

标签: c# multithreading locking

我有一个Windows服务应用程序,它使用Threading.TimerTimerCallback以特定间隔进行一些处理。我需要一次只将这个处理代码锁定到1个线程。

例如,启动服务并触发第一个回调并启动一个线程并开始处理。只要在下一次回调之前完成处理,这就可以正常工作。所以说比如说处理花费的时间比平时长一些,并且在另一个线程处理时再次触发TimerCallback,我需要让该线程等到另一个线程完成。

以下是我的代码示例:

static Timer timer;
static object locker = new object();

public void Start()
{
    var callback = new TimerCallback(DoSomething);
    timer = new Timer(callback, null, 0, 10000);
}

public void DoSomething()
{
      lock(locker)
      {
           // my processing code
      }
}

这是一种安全的方法吗?如果队列变得相当大,会发生什么?有更好的选择吗?

2 个答案:

答案 0 :(得分:32)

如果你可以在它们之间以恒定的间隔触发事件(与以恒定间隔激发它们的当前代码相反),那么你可以在没有句点的情况下启动计时器,并且每次排队时新的回调,例如

static Timer timer;

public void Start()
{
    var callback = new TimerCallback(DoSomething);
    timer = new Timer(callback, null, 0, Timeout.Infinite);
}

public void DoSomething()
{
      try
      {
           // my processing code
      }
      finally
      {
          timer.Change(10000, Timeout.Infinite);
      }
}

此代码告诉新创建的计时器立即触发,仅一次。在处理代码中,它完成工作,然后告诉计时器在10秒内再次触发,仅一次。因为定时器现在不是定期触发,而是通过其回调方法重新启动,所以回调保证是单线程的,没有队列。

如果你想保持一个恒定的间隔,那么它有点棘手,因为你必须决定如果处理开始花费的时间超过定时器间隔该怎么办。一种选择是执行您当前正在执行的操作,但这实际上会导致大量排队的线程和最终的线程池饥饿。另一种选择是,如果已经有一个回调,则简单地丢弃回调,例如

static Timer timer;
static object locker = new object();

public void Start()
{
    var callback = new TimerCallback(DoSomething);
    timer = new Timer(callback, null, 0, 10000);
}

public void DoSomething()
{
      if (Monitor.TryEnter(locker))
      {
           try
           {
               // my processing code
           }
           finally
           {
               Monitor.Exit(locker);
           }
      }
}

答案 1 :(得分:1)

如果处理代码执行时间超过10秒,最糟糕的情况是,每次调用一个新的回调时,你将浪费一个线程池线程(它们将在lock语句中等待)。如果你把所有的线程池线程HttpWebRequest,ASP.NET,异步委托调用......都会受到影响。

我要做的是立即安排第一次回调。然后,如果你真的需要每10秒调用一次 DoSomething()

public void DoSomething ()
{
       DateTime start = DateTime.UtcNow;
       ...
       TimeSpan elapsed = (DateTime.UtcNow - start);
       int due_in = (int) (10000 - elapsed.TotalMilliseconds);
       if (due_in < 0)
           due_in = 0;
       timer.Change (due_in, Timeout.Infinite);
}

或者那条线上的东西。