“原子地”改变System.Threading.Timer

时间:2008-12-01 23:55:21

标签: c# .net

假设我有一个现有的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)),如果原始Timer触发,则通过检查回调中的DateTime.Now来忽略它。 (我有一个唠叨的担心,由于使用TimeSpan的Timer和我使用DateTime的检查,这可能不是100%可靠...这可能不是问题,但由于某种原因我不是很满意... )

我的问题是:

  1. 我是否有一个好方法可以调用Timer.Change并且能够知道在回调排队到线程池之前我是否设法更改了它? (我不这么认为,但要求......并没有伤害。)
  2. 有没有其他人实施(我称之为)这样的“后推计时器”?如果是这样,我很想知道你是如何解决这个问题的。
  3. 这个问题在某种程度上是假设性的,因为我已经有了几个工作解决方案(基于Dispose并基于DateTime.Now)......我主要对听到与性能相关的建议感兴趣(因为我将会非常频繁地“推回”计时器。

    谢谢!

3 个答案:

答案 0 :(得分:1)

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

System.Windows.Forms.Application.Idle

答案 1 :(得分:1)

我将您的问题解释为请求实现下面指定的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);
            }
        }
    }
}

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

如果有人对我的初步想法感兴趣,请问我。

答案 2 :(得分:0)

我实际上必须为我制作的MMORPG建立自己的“Timing”课程。它可以跟踪超过100,000个“实体”,这些实体具有处理AI和其他任务的计时器。基于可以采取的不同行动,我不得不暂时延迟一个事件。

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

while (sleepyTime > 0)
{
    int temp = sleepyTime;
    sleepyTime = 0;
    Thread.Sleep(temp);
}

// here's where your actual code is.

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