【问题标题】:Synchronizing a timer to prevent overlap同步计时器以防止重叠
【发布时间】:2009-03-26 01:33:32
【问题描述】:

我正在编写一个 Windows 服务,它每隔一段时间运行一个可变长度的活动(数据库扫描和更新)。我需要经常运行此任务,但要处理的代码不能安全地同时运行多次。

我怎样才能最简单地设置一个计时器以每 30 秒运行一次任务,同时又不重叠执行? (我假设System.Threading.Timer 是这项工作的正确计时器,但可能是错误的)。

【问题讨论】:

  • 我不知道你是否已经解决了这个问题,但是带锁的 Monitor.Tryenter 是要走的路。只有在获得锁的情况下才会执行间隔代码。只有当线程尚未执行您的代码时,您才会获得锁。

标签: c# multithreading timer overlap


【解决方案1】:

您可以使用计时器来执行此操作,但您需要对数据库扫描和更新进行某种形式的锁定。一个简单的lock 进行同步可能足以防止发生多次运行。

话虽如此,最好在操作完成后启动一个计时器,只使用一次,然后停止它。下次操作后重新启动它。这将使您在事件之间有 30 秒(或 N 秒),没有重叠的机会,也没有锁定。

例子:

System.Threading.Timer timer = null;

timer = new System.Threading.Timer((g) =>
  {
      Console.WriteLine(1); //do whatever

      timer.Change(5000, Timeout.Infinite);
  }, null, 0, Timeout.Infinite);

立即工作.....完成...等待 5 秒....立即工作.....完成...等待 5 秒....

【讨论】:

  • 我支持这种方法 - '在操作完成后启动计时器可能会更好......'
  • 我第三次使用这种方法。您还可以动态计算计时器延迟以接近 30 秒。禁用计时器,获取系统时间,更新数据库,然后初始化下一个计时器以在保存时间后 30 秒触发。为安全起见,具有最小的计时器延迟。
  • 我们如何确保操作将在 5 秒内完成。 upvote 总数超过 @jsw,但看起来更有效。
  • 为什么很难找到这个解决方案.. 花了我几个小时
  • 如果我刷新它重叠的页面!
【解决方案2】:

我会在您经过的代码中使用 Monitor.TryEnter:

if (Monitor.TryEnter(lockobj))
{
  try
  {
    // we got the lock, do your work
  }
  finally
  {
     Monitor.Exit(lockobj);
  }
}
else
{
  // another elapsed has the lock
}

【讨论】:

    【解决方案3】:

    我更喜欢System.Threading.Timer 这样的事情,因为我不必通过事件处理机制:

    Timer UpdateTimer = new Timer(UpdateCallback, null, 30000, 30000);
    
    object updateLock = new object();
    void UpdateCallback(object state)
    {
        if (Monitor.TryEnter(updateLock))
        {
            try
            {
                // do stuff here
            }
            finally
            {
                Monitor.Exit(updateLock);
            }
        }
        else
        {
            // previous timer tick took too long.
            // so do nothing this time through.
        }
    }
    

    您可以通过将计时器设置为一次性并在每次更新后重新启动它来消除对锁定的需要:

    // Initialize timer as a one-shot
    Timer UpdateTimer = new Timer(UpdateCallback, null, 30000, Timeout.Infinite);
    
    void UpdateCallback(object state)
    {
        // do stuff here
        // re-enable the timer
        UpdateTimer.Change(30000, Timeout.Infinite);
    }
    

    【讨论】:

    • @RollerCosta 请解释为什么您认为选项 2 会创建多个线程。我读过的所有东西,以及我的经验,都告诉我它没有。计时器是一次性的,这意味着它会触发一次。然后在回调中我重新启用它,再次一次性。
    • 这就是我们在 Windows 服务中所拥有的,但它不起作用。重叠部分没有得到应有的处理。
    • @HermanVanDerBlom 我提出了两种不同的解决方案。您的服务中使用了哪一个,它是如何不起作用的?
    • 我使用锁定。这样可行。启动和停止不是因为如果将它放入 Windows 服务并且服务停止,则计时器可以在已经处理时再次启动。我派生了一个带有 Is Disposed 方法的 Timer 类和一个检查 Timer 是否已被释放的新 Start 方法。在这个解决方案之后,我使用了锁并发现了一个更好的解决方案。情况不是它阻塞而是现在不能重叠,所以启动/停止是没有必要的,感觉不对
    【解决方案4】:

    而不是锁定(这可能会导致所有定时扫描等待并最终叠加)。您可以在线程中启动扫描/更新,然后检查线程是否仍然存在。

    Thread updateDBThread = new Thread(MyUpdateMethod);
    

    ...

    private void timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        if(!updateDBThread.IsAlive)
            updateDBThread.Start();
    }
    

    【讨论】:

    • 是的,但是如果您在经过的时间内运行扫描/更新,您将无法处理它来检查它是否还活着。
    【解决方案5】:

    您可以按如下方式使用 AutoResetEvent:

    // Somewhere else in the code
    using System;
    using System.Threading;
    
    // In the class or whever appropriate
    static AutoResetEvent autoEvent = new AutoResetEvent(false);
    
    void MyWorkerThread()
    {
       while(1)
       {
         // Wait for work method to signal.
            if(autoEvent.WaitOne(30000, false))
            {
                // Signalled time to quit
                return;
            }
            else
            {
                // grab a lock
                // do the work
                // Whatever...
            }
       }
    }
    

    一个稍微“聪明”的解决方案是伪代码如下:

    using System;
    using System.Diagnostics;
    using System.Threading;
    
    // In the class or whever appropriate
    static AutoResetEvent autoEvent = new AutoResetEvent(false);
    
    void MyWorkerThread()
    {
      Stopwatch stopWatch = new Stopwatch();
      TimeSpan Second30 = new TimeSpan(0,0,30);
      TimeSpan SecondsZero = new TimeSpan(0);
      TimeSpan waitTime = Second30 - SecondsZero;
      TimeSpan interval;
    
      while(1)
      {
        // Wait for work method to signal.
        if(autoEvent.WaitOne(waitTime, false))
        {
            // Signalled time to quit
            return;
        }
        else
        {
            stopWatch.Start();
            // grab a lock
            // do the work
            // Whatever...
            stopwatch.stop();
            interval = stopwatch.Elapsed;
            if (interval < Seconds30)
            {
               waitTime = Seconds30 - interval;
            }
            else
            {
               waitTime = SecondsZero;
            }
         }
       }
     }
    

    其中任何一个的优点是您可以关闭线程,只需发出事件信号。


    编辑

    我应该补充一点,这段代码假设您只运行其中一个 MyWorkerThreads(),否则它们会同时运行。

    【讨论】:

      【解决方案6】:

      当我想要单次执行时,我使用了互斥锁:

          private void OnMsgTimer(object sender, ElapsedEventArgs args)
          {
              // mutex creates a single instance in this application
              bool wasMutexCreatedNew = false;
              using(Mutex onlyOne = new Mutex(true, GetMutexName(), out wasMutexCreatedNew))
              {
                  if (wasMutexCreatedNew)
                  {
                      try
                      {
                            //<your code here>
                      }
                      finally
                      {
                          onlyOne.ReleaseMutex();
                      }
                  }
              }
      
          }
      

      抱歉,我来晚了...您需要在 GetMutexName() 方法调用中提供互斥锁名称。

      【讨论】:

        猜你喜欢
        • 2010-09-17
        • 1970-01-01
        • 1970-01-01
        • 2012-12-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-04
        • 2019-07-24
        相关资源
        最近更新 更多