使用易失性布尔创建一个非重入“非暂停”计时器

时间:2019-02-15 14:47:41

标签: c# timer

我已经看到了许多示例(here和其他示例),这些方法通过在调用经过的处理程序方法时停止计时器,并在经过的处理程序方法结束时再次启动计时器来创建非重入计时器。这似乎是推荐的方法。这种方法的问题是,在运行“经过的处理程序方法”时,您会在时间上有间隔。您最终可能会在很短的时间内造成大量时间偏离。

因此,我正在考虑一种更好的方法,我可以使用布尔值确定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();
        }
    }
}

1 个答案:

答案 0 :(得分:0)

我会提出这个简单的异步模式。它每Action执行给定的ts,但是在开始当前迭代之前,在之前开始倒数计时。如果执行花费的时间超过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
    }
}