[英]Creating a Non-Reentrant “Non-Pausing” Timer using a volatile bool
我已經看到了很多示例( 在這里和其他地方),通過在調用經過的處理程序方法時停止計時器,然后在經過的處理程序方法結束時再次啟動計時器,來創建非重入計時器。 這似乎是推薦的方法。 這種方法的問題是,在運行“經過的處理程序方法”時,您會在時間上有間隔。 您最終可能會在很短的時間內造成大量時間偏離。
因此,我正在考慮一種更好的方法,我可以使用布爾值來確定Timer的狀態,並且不管Elapsed Handler當前是否正在運行,是否正在運行,那么對Elapsed Handler的調用就是立即返回,其余未執行。
以下是基本思路
volatile bool _IsProcessingElapsedMethod = false;
private void _timer_Elapsed(object sender, ElapsedEventArgs e)
{
if (_IsProcessingElapsedMethod)
{
Console.WriteLine("Warning: Re-Entrance was attempted and Ignored.");
return;
}
_IsProcessingElapsedMethod = true;
//** DO Something here
_IsProcessingElapsedMethod = false;
}
一定有一個原因,我從未見過有人這樣做。 我是否缺少一些明顯的陷阱? 這似乎是一個非常簡單的解決方案。
下面是一個可編譯的示例。
using System;
using System.Threading.Tasks;
using System.Timers;
namespace QuestionNon_ReEntrantTimer
{
class Program
{
static private int Timer1_ElapsedCount = 1;
static void Main(string[] args)
{
NonReEntrantTimer timer1 = new NonReEntrantTimer(500);
timer1.Elapsed += Timer1_Elapsed;
timer1.Start();
Console.WriteLine("Press Any key to Exit");
Console.ReadKey();
}
private static void Timer1_Elapsed(object sender, ElapsedEventArgs e)
{
int delayTime;
if(Timer1_ElapsedCount < 10)
{
delayTime = 300 * Timer1_ElapsedCount++;
}
else
{
Timer1_ElapsedCount++;
delayTime = 400;
}
Console.WriteLine($"Timer1_Elapsed Call Count is {Timer1_ElapsedCount} Waiting for {delayTime} ms");
Task.Delay(delayTime).Wait();
}
}
public class NonReEntrantTimer : IDisposable
{
Timer _timer = new Timer();
public event ElapsedEventHandler Elapsed;
volatile bool _IsProcessingElapsedMethod = false;
public NonReEntrantTimer(double interval)
{
_timer = new Timer(interval);
_timer.Elapsed += _timer_Elapsed;
}
public void Start() => _timer.Start();
public void Stop() => _timer.Stop();
public void Close() => _timer.Close();
private void _timer_Elapsed(object sender, ElapsedEventArgs e)
{
if (_IsProcessingElapsedMethod)
{
Console.WriteLine("Warning: Re-Entrance was attempted and Ignored.");
return;
}
_IsProcessingElapsedMethod = true;
Elapsed?.Invoke(sender, e);
_IsProcessingElapsedMethod = false;
}
public void Dispose()
{
_timer.Dispose();
}
}
}
我會提出這種簡單的異步模式。 它每ts
執行一次給定的Action
,但是在開始當前迭代之前 ,開始倒計時到下一次執行。 如果執行花費的時間比ts
,則將下一次迭代推遲到上一次完成之后。
async Task ExecuteEvery(TimeSpan ts, Action a, CancellationToken ct)
{
try
{
var currentDelay = Task.Delay(ts, ct);
while (!ct.IsCancellationRequested)
{
await currentDelay; // waiting for the timeout
currentDelay = Task.Delay(ts, ct); // timeout finished, starting next wait
a(); // executing action in the meanwhile
}
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
// if we are cancelled, nothing to do, just exit
}
}
您可以通過取消令牌來停止迭代。 您可以通過使用Task.Run
啟動操作,將操作執行卸載到線程池中。
更新:如果您希望計時器在緩慢的動作后嘗試趕上,則可以進行一些小的更改:
async Task ExecuteEvery(TimeSpan ts, Action a, CancellationToken ct)
{
try
{
for (var targetTime = DateTime.Now + ts; !ct.IsCancellationRequested; targetTime += ts)
{
var timeToWait = targetTime - DateTime.Now;
if (timeToWait > TimeSpan.Zero)
await Task.Delay(timeToWait, ct);
a();
}
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
// if we are cancelled, nothing to do, just exit
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.