【问题标题】:Using lock with Threading.Timer将锁与 Threading.Timer 一起使用
【发布时间】:2009-11-08 23:48:25
【问题描述】:

我有一个 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
      }
}

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

【问题讨论】:

    标签: c# multithreading locking


    【解决方案1】:

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

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

    【讨论】:

    • 触发计时器的好主意从来没有这样想过。第二种情况,这是否意味着线程只是退出而不等待?
    • 是的,在第二种情况下,如果当前正在处理代码中存在另一个线程,则 TryEnter 立即返回,因此它有效地跳过了该计时器间隔。
    • 太棒了!我在 JavaScript 中使用第一个场景。不知道如何在 C# 中实现。
    【解决方案2】:

    如果处理代码的执行时间超过 10 秒,则可能发生的最糟糕的情况是,每次调用新的回调时,您将浪费 1 个线程池线程(它们将在 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);
    }
    

    或者类似的东西。

    【讨论】:

      猜你喜欢
      • 2017-05-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-26
      相关资源
      最近更新 更多