【问题标题】:"Atomically" changing a System.Threading.Timer“原子地”更改 System.Threading.Timer
【发布时间】:2008-12-01 23:55:21
【问题描述】:

假设我有一个现有的 System.Threading.Timer 实例,我想对其调用 Change 以将其触发时间推迟:

var timer = new Timer(DelayCallback, null, 10000, Timeout.Infinite);
// ... (sometime later but before DelayCallback has executed)
timer.Change(20000, Timeout.Infinite);

我正在使用此计时器在一段时间没有活动后执行“空闲回调”。 (在这种情况下,“空闲”和“无活动”是应用程序定义的条件......细节并不是非常重要。)每次我执行“动作”时,我都想重置计时器以使其始终设置10 秒后触发。

但是,存在固有的竞争条件,因为当我调用 Change 时,我无法判断 Timer 是否已根据其旧设置触发。 (当然,我可以判断我的回调是否发生,但我无法判断 CLR 的内部计时器线程是否已将我的回调排队到线程池并且它的执行即将执行。)

现在我知道我可以在计时器实例上调用 Dispose 并在每次需要“推回”时重新创建它。但这似乎比仅仅改变现有的计时器效率低。当然它可能不是...我稍后会运行一些微基准测试并让大家知道。

或者,我可以始终跟踪预期的触发时间(通过 DateTime.Now.AddSeconds(10)),如果原始计时器触发,则通过在回调中检查 DateTime.Now 来忽略它。 (我担心这可能不是 100% 可靠,因为使用 TimeSpan 的 Timer 和使用 DateTime 的检查......这可能不是问题,但由于某种原因我对它并不完全满意...... )

我的问题是:

  1. 有没有一种好方法可以让我调用 Timer.Change 并能够知道在回调排队到线程池之前我是否设法更改它? (我不这么认为,但问起来也无妨……)
  2. 有没有其他人实施过(我称之为)这样的“后推计时器”?如果是这样,我很想听听您是如何解决这个问题的。

这个问题本质上是假设性的,因为我已经有几个可行的解决方案(基于 Dispose 和基于 DateTime.Now)...我主要对听取与性能相关的建议感兴趣(因为我将非常频繁地“推回”计时器)。

谢谢!

【问题讨论】:

    标签: c# .net


    【解决方案1】:

    听起来你真正想要的是应用程序空闲事件

    System.Windows.Forms.Application.Idle
    

    【讨论】:

      【解决方案2】:

      我将您的问题解释为对实现下面指定的 IdleNotifier 接口的请求。您还声明 ActionOccured() 需要快速。

      public delegate void IdleCallback();
      public interface IdleNotifier
      {
          // Called by threadpool when more than IdleTimeSpanBeforeCallback 
          // has passed since last call on ActionOccured.
          IdleCallback Callback { set; }
          TimeSpan IdleTimeSpanBeforeCallback { set; }
          void ActionOccured();
      }
      

      我在下面提供了一个带有 System.Threading.Timer 的实现。 关于实施的要点:

      • 我们接受计时器可以随时唤醒并确保一切正常。
      • 由于我们假设定时器唤醒相对较少,我们可以在这些时间做昂贵的工作。
      • 由于我们可以在定时器回调中执行所有逻辑,因此“推送定时器”所需要做的就是记住我们上次推送它的时间。

      实施:

      public class IdleNotifierTimerImplementation : IdleNotifier
      {
          private readonly object SyncRoot = new object();
          private readonly Timer m_Timer;
      
          private IdleCallback m_IdleCallback = null;
          private TimeSpan m_IdleTimeSpanBeforeEvent = TimeSpan.Zero;
      
          // Null means there has been no action since last idle notification.
          private DateTime? m_LastActionTime = null;
      
          public IdleNotifierTimerImplementation()
          {
              m_Timer = new Timer(OnTimer);
          }
      
          private void OnTimer(object unusedState)
          {
              lock (SyncRoot)
              {
                  if (m_LastActionTime == null)
                  {
                      m_Timer.Change(m_IdleTimeSpanBeforeEvent, TimeSpan.Zero);
                      return;
                  }
                  TimeSpan timeSinceLastUpdate = DateTime.UtcNow - m_LastActionTime.Value;
                  if (timeSinceLastUpdate > TimeSpan.Zero)
                  {
                      // We are no idle yet.
                      m_Timer.Change(timeSinceLastUpdate, TimeSpan.Zero);
                      return;
                  }
                  m_LastActionTime = null;
                  m_Timer.Change(m_IdleTimeSpanBeforeEvent, TimeSpan.Zero);
              }
              if (m_IdleCallback != null)
              {
                  m_IdleCallback();
              }
          }
      
          // IdleNotifier implementation below
      
          public void ActionOccured()
          {
              lock (SyncRoot)
              {
                  m_LastActionTime = DateTime.UtcNow;
              }
          }
      
          public IdleCallback Callback
          {
              set
              {
                  lock (SyncRoot)
                  {
                      m_IdleCallback = value;
                  }
              }
          }
      
          public TimeSpan IdleTimeSpanBeforeCallback
          {
              set
              {
                  lock (SyncRoot)
                  {
                      m_IdleTimeSpanBeforeEvent = value;
                      // Run OnTimer immediately
                      m_Timer.Change(TimeSpan.Zero, TimeSpan.Zero);
                  }
              }
          }
      }
      

      此代码有许多直接的性能改进。

      如果有人对我的第一个想法感兴趣,请问我。

      【讨论】:

        【解决方案3】:

        实际上,我必须为自己制作的 MMORPG 构建自己的“时间”类。它可以跟踪超过 100,000 个具有处理 AI 和其他任务的计时器的“实体”。根据可以采取的不同行动,我将不得不暂时延迟一个事件。

        现在,我的计时课完全是手写的,所以它不会完全是您想要的。但是你可以做的事情类似于我想出的解决方案是做一种:

        while (sleepyTime > 0)
        {
            int temp = sleepyTime;
            sleepyTime = 0;
            Thread.Sleep(temp);
        }
        
        // here's where your actual code is.
        

        然后,您可以制作一个基本上只是向 sleepyTime 投放广告的“延迟”方法。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-28
          • 1970-01-01
          • 2020-12-07
          • 1970-01-01
          相关资源
          最近更新 更多