【问题标题】:How to let Timer skip tick if the previous thread is still busy如果前一个线程仍然忙,如何让 Timer 跳过滴答声
【发布时间】:2011-03-26 09:23:54
【问题描述】:

我创建了一个 Windows 服务,它应该每 60 秒检查一次数据库中的某个表是否有新行。对于添加的每一行,我需要在服务器上进行一些繁重的处理,有时可能需要超过 60 秒。

我在我的服务中创建了一个 Timer 对象,它每 60 秒计时一次并调用所需的方法。
由于我不希望这个计时器在处理找到的新行时计时,所以我将该方法包装在 lock { } 块中,因此其他线程将无法访问它。

看起来像这样:

Timer serviceTimer = new Timer();
serviceTimer.Interval = 60;
serviceTimer.Elapsed += new ElapsedEventHandler(serviceTimer_Elapsed);
serviceTimer.Start();

void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
{
    lock (this)
    {
        // do some heavy processing...
    }
}

现在,我想知道 -
如果我的计时器滴答作响,并且在 db 上发现了很多新行,现在处理将花费 60 秒以上,那么下一个滴答将不会进行任何处理,直到前一个完成。这就是我想要的效果。

但是现在,serviceTimer_Elapsed 方法会在第一次处理完成后立即关闭,还是等待计时器再次计时。

我想要发生的是 - 如果处理需要超过 60 秒,那么计时器会注意到线程被锁定,然后再等待 60 秒再次检查,这样我就永远不会陷入有等待前一个线程完成的线程队列。

我怎样才能完成这个结果?
这样做的最佳做法是什么?

谢谢!

【问题讨论】:

    标签: c# multithreading windows-services timer


    【解决方案1】:

    使用 Reactive Extensions 有一种非常巧妙的方法可以解决这个问题。这是代码,您可以在此处阅读更完整的说明:http://www.zerobugbuild.com/?p=259

    public static IDisposable ScheduleRecurringAction(
        this IScheduler scheduler,
        TimeSpan interval,
        Action action)
    {
        return scheduler.Schedule(
            interval, scheduleNext =>
        {
            action();
            scheduleNext(interval);
        });
    }
    

    你可以这样使用它:

    TimeSpan interval = TimeSpan.FromSeconds(5);
    Action work = () => Console.WriteLine("Doing some work...");
    
    var schedule = Scheduler.Default.ScheduleRecurringAction(interval, work);          
    
    Console.WriteLine("Press return to stop.");
    Console.ReadLine();
    schedule.Dispose();
    

    【讨论】:

    • 这是一个非常酷的解决方案,展示了 RX 调度程序的有趣用法,但可能不是很明显。
    • 这个解决方案对我来说效果很好。也让我对调度程序有了新的认识
    【解决方案2】:

    您可以尝试在处理过程中禁用计时器,例如

    // Just in case someone wants to inherit your class and lock it as well ...
    private static object _padlock = new object();
    try
    {
      serviceTimer.Stop(); 
    
      lock (_padlock)
        { 
            // do some heavy processing... 
        } 
    }
    finally
    {
      serviceTimer.Start(); 
    }
    

    编辑:OP 没有指定重入是仅由计时器引起还是服务是多线程的。假设是后者,但如果是前者,那么如果计时器停止(自动重置或手动),则不需要锁定

    【讨论】:

    【解决方案3】:

    其他选项可能是使用 BackGroundWorker 类或 TheadPool.QueueUserWorkItem。

    后台工作人员可以轻松地为您提供选项检查当前处理是否仍在进行,并一次处理 1 个项目。 ThreadPool 将使您能够在每个滴答声(如有必要)继续将项目排队到后台线程。

    根据您的描述,我假设您正在检查数据库中队列中的项目。在这种情况下,我会使用 ThreadPool 将工作推送到后台,而不是减慢/停止您的检查机制。

    对于服务,我真的建议您考虑使用 ThreadPool 方法。这样,您可以使用计时器每 60 秒检查一次新项目,然后将它们排队,让 .Net 计算分配给每个项目的数量,然后继续将项目推入队列。

    例如:如果您只使用计时器并且有 5 个新行,总共需要 65 秒的处理时间。使用 ThreadPool 方法,这将在 65 秒内完成,有 5 个后台工作项。使用 Timer 方法,这将需要 4 多分钟(您将在每行之间等待的时间),而且这可能会导致其他正在排队的工作积压。

    这是一个应该如何完成的示例:

    Timer serviceTimer = new Timer();
        void startTimer()
        {
            serviceTimer.Interval = 60;
            serviceTimer.Elapsed += new ElapsedEventHandler(serviceTimer_Elapsed);
            serviceTimer.AutoReset = false;
            serviceTimer.Start();
        }
        void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            try
            {
                // Get your rows of queued work requests
    
                // Now Push Each Row to Background Thread Processing
                foreach (Row aRow in RowsOfRequests)
                {
                    ThreadPool.QueueUserWorkItem(
                        new WaitCallback(longWorkingCode), 
                        aRow);
                }
            }
            finally
            {
                // Wait Another 60 Seconds and check again
                serviceTimer.Stop();
            }
        }
    
        void longWorkingCode(object workObject)
        {
            Row workRow = workObject as Row;
            if (workRow == null)
                return;
    
            // Do your Long work here on workRow
        }
    

    【讨论】:

      【解决方案4】:

      其他答案的类似变体,它允许计时器保持滴答作响,并且仅在可以获得锁时才执行工作,而不是停止计时器。

      把它放在经过的事件处理程序中:

      if (Monitor.TryEnter(locker)
      {
          try
          {
              // Do your work here.
          }
          finally
          {
              Monitor.Exit(locker);
          }
      }
      

      【讨论】:

      • 这也是我使用的方法。 elapsed 事件处理程序刚刚结束,计时器可以不理会。​​span>
      【解决方案5】:

      另一种可能性是这样的:

      void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
      {   
          if (System.Threading.Monitor.IsLocked(yourLockingObject))
             return;
          else
             lock (yourLockingObject)
             // your logic  
                 ;
      }
      

      【讨论】:

        【解决方案6】:

        我建议您在处理时不要让计时器滴答作响。

        将 Timers AutoReset 设置为 false。并在最后开始。这是您可能感兴趣的完整答案 Needed: A Windows Service That Executes Jobs from a Job Queue in a DB; Wanted: Example Code

        【讨论】:

          【解决方案7】:

          在这种情况下,您不需要锁。在启动之前设置 timer.AutoReset=false。 完成处理后,重新启动处理程序中的计时器。这将确保计时器在每个任务之后触发 60 秒。

          【讨论】:

            【解决方案8】:

            快速检查一下服务是否正在运行。如果它正在运行,它将跳过此事件并等待下一个事件触发。

            Timer serviceTimer = new Timer();
            serviceTimer.Interval = 60;
            serviceTimer.Elapsed += new ElapsedEventHandler(serviceTimer_Elapsed);
            serviceTimer.Start();
            bool isRunning = false;
            void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
            {
                lock (this)
                {
                    if(isRunning)
                        return;
                    isRunning = true;
                }
                try
                {
                // do some heavy processing...
                }
                finally
                {
                    isRunning = false;
                }
            }
            

            【讨论】:

            • 我比我更喜欢 nonnb 的解决方案,但是当您无法阻止事件触发时,我会留下我的示例。
            • 另外,允许事件继续触发在某些情况下会很有用。您可以记录它或在主进程之外进行一些其他处理。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多