定时器完成后重启功能
Posted
技术标签:
【中文标题】定时器完成后重启功能【英文标题】:Timer to restart function when completed 【发布时间】:2020-09-12 01:34:25 【问题描述】:我目前遇到了计时器问题。
我有一个每隔 x 秒由计时器启动的功能。 现在,在不同的条件下,函数执行有时会花费更长的时间。 所以我希望计时器仅在完成功能后重新运行。 我怎样才能做到这一点?
【问题讨论】:
假设您希望每 10 秒触发一次对您的方法的调用。如果调用需要 7 秒,则在它返回后将有 3 秒的延迟,直到下一个调用。如果通话需要 12 秒,您是要等待 8 秒等待下一次通话,还是要“同步”计时器并立即触发,然后再次触发 22 秒?换句话说,您能否解释一下您正在寻找的确切行为? 嗨 Lasse,感谢您的回复。我希望它在完成后立即重新触发。 好的,所以需要更多说明。通话需要 12 秒,当它结束时你想再次触发通话,假设这个通话只需要 2 秒(我们现在在整个事情开始后长达 14 秒)。您是要等待 6 秒等待第三次通话,还是 8 秒,换句话说,您是想从 12 秒开始重新开始 10 秒的循环,还是要保持原来的时间表,只需处理尽可能延长通话时间? 我只想让它尽快重新运行。所以我真的不在乎方法需要多长时间才能完成,但是当它完成时,它必须立即/尽快重新开始。我在想是否有办法让计时器知道该方法已经完成(一些信号或状态,也许让计时器开始一个任务并使用 taskcompleted 什么的?) 您是否处于任何 UI 形式? 【参考方案1】:就在它退出之前,您的函数可能会触发一个事件。该事件将被另一个函数捕获,该函数将启动计时器。
【讨论】:
我想这对我来说是最好的方式。我现在创建一个包含这样的事件的类:class MyClass public event EventHandler MyEvent; public void Method() MyEvent?.Invoke(this, EventArgs.Empty);像这样使用它: MyClass myObject = new MyClass(); myObject.MyEvent += new EventHandler(myObject_MyEvent); myObject.Method();然后事件中的方法将启动一个只运行一次的计时器,并在函数结束时重新触发。【参考方案2】:我的建议是避免为此使用 Timer 类。造成这种情况的原因正是你现在处理这个问题时遇到的问题。
System.Threading.Timer 类是可重入的,这意味着如果在上一个触发器调用返回之前间隔过去,即使它已经在执行,它也会再次调用该方法。您需要以一种使代码有点混乱的方式来处理方法和触发器之间的协作。
但是,让我们重新考虑一下这个问题。
你想要
-
按时间间隔调用方法
如果某个方法调用的执行时间比间隔时间长,则立即再次触发
更好的方法是省去触发器并编写自己的使用任务。
这是一个非常简单的例子:
public static async Task CallPeriodicAsync(Func<CancellationToken, Task> func,
int intervalMilliseconds, CancellationToken cancellationToken)
while (true)
var delay = Task.Delay(intervalMilliseconds, cancellationToken);
await func(cancellationToken);
await delay;
你可以这样开始:
CancellationToken cancellationToken = ...;
CallPeriodicAsync(async ct =>
int timeToRun = 1000 + r.Next(14000);
Console.WriteLine($"This time running for timeToRun ms");
await Task.Delay(timeToRun, ct);
, 10000, cancellationToken);
示例输出将是(我在 CallPeriod 方法中也有一些 Console.WriteLine 来说明它在这次运行中是等待还是立即触发):
This time running for 12162 ms
trigger again immediately
This time running for 14706 ms
trigger again immediately
This time running for 12756 ms
trigger again immediately
This time running for 2187 ms
delay until next is 7813 ms
This time running for 5221 ms
delay until next is 4767 ms
This time running for 8866 ms
基本上这个 CallPeriod 方法会调用你的方法并给它 X 毫秒来完成。如果它完成得更快,它将为剩余部分增加一个延迟,如果它完成得较慢,它将重新开始循环。
这意味着我关于“保持原始间隔”的问题仍然相关。
例如,保持原始间隔会导致这种触发(---*
只是时间线),每 4 秒一次:
v v v v v
*---*---*---*---*---*---*---*---*---*---*---*---*---*---*---*---*---*
[...] [..................][.......] [...] [....
而我上面的解决方案会在延迟后扭曲循环:
v v v v v
*---*---*---*---*---*---*---*---*---*---*---*---*---*---*---*---*---*
[...] [..................][.......] [...] [....
v ^
+-- because we got -+
one second delayed
here we're now one
second late always
【讨论】:
非常好!一些建议:1) 添加Async
后缀,2) 将while (!cancellationToken.IsCancellationRequested)
替换为while(true)
,以避免出现不一致的取消行为(有时会抛出,有时不会)和3) 在等待之前创建Task.Delay
任务func
和 await
之后,摆脱 Stopwatch
和相关的时间计算。以上是关于定时器完成后重启功能的主要内容,如果未能解决你的问题,请参考以下文章