【问题标题】:Synchronizing a Timers.Timer elapsed method when stopping停止时同步 Timers.Timer elapsed 方法
【发布时间】:2013-07-25 15:36:08
【问题描述】:

参考MSDN 关于 System.Timers.Timer 的这句话:

Timer.Elapsed 事件在 ThreadPool 线程,所以 事件处理方法可能在一个上运行 线程同时调用 Timer.Stop 方法在另一个上运行 线。这可能会导致 在 调用停止方法。这场比赛 条件不能简单地预防 通过比较 SignalTime 属性 随着 Stop 方法的时间 调用,因为事件处理 方法可能已经在执行 调用 Stop 方法,或者可能 瞬间开始执行 当调用 Stop 方法并且 保存停止时间的时刻。如果 防止线程是至关重要的 调用 Stop 方法 在事件处理过程中进行 方法仍在执行,使用更多 强大的同步机制,例如 作为 Monitor 类或 比较交换方法。使用的代码 CompareExchange 方法可以是 在示例中找到 Timer.Stop 方法。

谁能举一个“强大的同步机制,例如 Monitor 类”的例子来解释这究竟意味着什么?

我认为这意味着以某种方式使用锁,但我不确定您将如何实现它。

【问题讨论】:

    标签: c# .net synchronization timer


    【解决方案1】:

    可靠地停止 System.Timers.Timer 确实是一项重大工作。最严重的问题是它用来调用 Elapsed 事件的线程池线程可以由于线程池调度程序算法而备份。有几个备用电话并不罕见,有数百个在技术上是可能的。

    您需要两个同步,一个确保仅在没有 Elapsed 事件处理程序运行时停止计时器,另一个确保这些备份的 TP 线程不会造成任何伤害。像这样:

        System.Timers.Timer timer = new System.Timers.Timer();
        object locker = new object();
        ManualResetEvent timerDead = new ManualResetEvent(false);
    
        private void Timer_Elapsed(object sender, ElapsedEventArgs e) {
            lock (locker) {
                if (timerDead.WaitOne(0)) return;
                // etc...
            }
        }
    
        private void StopTimer() {
            lock (locker) {
                timerDead.Set();
                timer.Stop();
            }
        }
    

    考虑将 AutoReset 属性设置为 false。另一种方式很脆弱,Elapsed 事件是从捕获异常的内部 .NET 方法调用的。非常讨厌,您的计时器代码在没有任何诊断的情况下停止运行。我不知道历史,但肯定有另一个 MSFT 团队对这个烂摊子大发雷霆,写了 System.Threading.Timer。强烈推荐。

    【讨论】:

    • 哇,计时器真是阴险的野兽。 :o
    • 是的,当你在飞回家的飞机上时,它会掉下来。气喘吁吁,在登机前进行压力测试。
    • 经过一番反思,似乎它在幕后创建了一个 Threading.Timer ,并且回调确实吞下了异常。基本的 Threading.Timer 的实现本身看起来要干净得多,所以我将对此进行阅读。
    • 嗨,汉斯。很抱歉评论这么老的帖子,但我认为timerDead.WaitOne() 实际上应该是timerDead.WaitOne(0)。一个小(我无法直接编辑)但很重要的区别。
    【解决方案2】:

    这就是它的建议。

    Monitor 是 C# 编译器用于 lock 语句的类。

    话虽如此,上述问题仅在您遇到问题时才是问题。整个语句基本上翻译为“您可能会在调用 Stop() 后立即发生计时器事件。如果这是一个问题,您将需要处理它。”根据您的计时器正在做什么,这可能是一个问题,也可能不是。

    如果这是一个问题,Timer.Stop 页面会显示一个可靠的方法(使用Interlocked.CompareExchange)来处理这个问题。只需复制示例中的代码并根据需要进行修改。

    【讨论】:

      【解决方案3】:

      试试:

      lock(timer) {
      timer.Stop();
      }
      

      【讨论】:

      • 这不会有任何影响,至少在没有相当多的其他代码的情况下不会产生任何影响......此外,这将是一个糟糕的同步机制选择。
      【解决方案4】:

      这是防止这种竞争条件发生的一种非常简单的方法:

      private object _lock = new object();
      private Timer _timer; // init somewhere else
      
      public void StopTheTimer()
      {
          lock (_lock) 
          {
              _timer.Stop();
          }
      }
      
      void elapsed(...)
      {
          lock (_lock)
          {
              if (_timer.Enabled) // prevent event after Stop() is called
              {
                  // do whatever you do in the timer event
              }
          }
      }
      

      【讨论】:

      • 这实际上不会阻止提到的竞争条件。 timerTick 可以与调用 _timer.Stop() 同时发生 - 并且之后仍然会被触发(此时,锁将被释放)。
      • 我从引用的文本中认为,如果事件处理方法正在执行,目标是防止计时器停止。我的代码会做到这一点(我认为)。
      • "这可能会导致在调用 Stop 方法后引发 Elapsed 事件。" - 使用此选项仍然会发生这种情况。 - 并且经过的方法的主体仍将完整执行,因为它没有检查计时器是否已停止...
      • 已修改。这可能不是最好的方法,但至少很简单。
      【解决方案5】:

      似乎计时器不是线程安全的。您必须通过锁定保持对它的所有调用同步。 lock(object){} 实际上只是一个简单的监视器调用的简写。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-05-13
        • 2019-06-01
        • 1970-01-01
        • 2013-03-14
        • 2023-01-17
        • 2013-04-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多