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

问题描述 投票:0回答:3

假设我有一个现有的 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 时,我无法根据旧设置判断计时器是否已经触发。 (当然,我可以判断我的回调是否已发生,但我无法判断 CLR 的内部计时器线程是否已将我的回调排队到线程池并且其执行即将到来。)

现在我知道我可以在计时器实例上调用 Dispose 并在每次需要“将其推回”时重新创建它。但这似乎比仅仅更改现有计时器效率低。当然,它可能不会...我稍后会运行一些微基准测试并让大家知道。 或者,我可以始终跟踪预期的触发时间(通过 DateTime.Now.AddSeconds(10)),并且如果原始计时器触发,则通过在回调中检查 DateTime.Now 来忽略它。 (我一直担心,由于使用 TimeSpan 的计时器和使用 DateTime 的检查,这可能不是 100% 可靠......这可能不是问题,但由于某种原因我对此并不完全满意...... )

我的问题是:

有没有一种好方法可以让我调用 Timer.Change 并能够知道在回调排队到线程池之前我是否设法更改它? (我不这么认为,但问一下也没什么坏处......)
  1. 还有其他人实施过(我所说的)这样的“推回计时器”吗?如果是这样,我很想听听您是如何解决这个问题的。
  2. 这个问题本质上是假设的,因为我已经有了几个工作解决方案(基于 Dispose 和基于 DateTime.Now)...我主要对听到与性能相关的建议感兴趣(因为我将“推动非常频繁地返回”计时器)。

谢谢!

c# .net
3个回答
1
投票

System.Windows.Forms.Application.Idle



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); } } } }

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

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


0
投票

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.