优秀的编程知识分享平台

网站首页 > 技术文章 正文

在C#中构建自定义防重入定时器Timer

nanyue 2024-09-11 05:30:31 技术文章 8 ℃

定时器是软件开发中至关重要的组件,可以在特定时间间隔内执行任务。在本文中,我将探讨如何在C#中创建一个自定义定时器,它不仅提供定时器的基本功能,还包括防止重入和监视执行时间等功能。定时器名字不知道取啥好,就叫AppTimer吧!

AppTimer类属性和事件

AppTimer类包括几个属性和事件:

  • ElapsedHandler:表示定时器到期时要执行的操作的事件。
  • ExceptionHandler:处理定时器执行过程中的异常的事件。
  • Period:表示定时器应该到期的间隔时间的整数属性(以毫秒为单位)。
  • RunOnStart:一个布尔属性,指示定时器是否在实例化时立即启动。
  • Name:用于指定定时器名称的字符串属性。
  • OpenDiagnostics:一个布尔属性,确定是否启用性能诊断。

实现细节

  • _taskTimer:Timer类的实例,用于安排定时器事件。
  • _performingTasks:一个易失性布尔标志,指示定时器当前是否正在执行任务。
  • _isRunning:一个易失性布尔标志,指示定时器是否处于活动状态。
  • sw:用于性能监控的Stopwatch实例。

构造函数和初始化

该类提供了两个构造函数。默认构造函数使用默认值初始化_taskTimer。第二个构造函数允许指定定时器的名称、周期和其他可选参数。

Start方法

Start方法启动定时器,确保周期有效,然后启动定时器。它还包括一个CancellationToken参数,以支持取消定时器。

主要特点

防止重入

为了防止重入,使用_performingTasks标志来指示定时器当前是否正在执行任务。这确保了在先前的任务仍在运行时,定时器不会启动新任务。

监视执行时间

OpenDiagnostics属性允许启用性能诊断。当设置为true时,定时器利用Stopwatch来测量任务的执行时间。

完整代码

/// <summary>
    /// 应用定时器
    /// 定时器主要功能有:防止重入,监控执行时间
    /// </summary>
    public class AppTimer : IDisposable
    {
        /// <summary>
        /// 定时器执行的业务处理.
        /// </summary>
        public event EventHandler ElapsedHandler;

        /// <summary>
        /// 异常处理
        /// </summary>
        public event EventHandler<Exception> ExceptionHandler;
        /// <summary>
        /// 间隔时间(单位毫秒).
        /// </summary>
        public int Period { get; set; }

        /// <summary>
        /// 启动就开始.
        /// </summary>
        public bool RunOnStart { get; set; }

        /// <summary>
        /// 定时器名称
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 是否开启性能诊断
        /// </summary>
        public bool OpenDiagnostics { get; set; }

        private readonly Timer _taskTimer; //定时器
        private volatile bool _performingTasks;  // 当前定时器是否正在执行任务
        private volatile bool _isRunning; // 定时器是否运行
        private Stopwatch sw;        // 性能整顿

        public AppTimer()
        {
            _taskTimer = new Timer(TimerCallBack, null, Timeout.Infinite, Timeout.Infinite);
        }

        public AppTimer(string name, int period, bool isRunontart = false, bool openDiagnostics = true) : this()
        {
            Name = name;
            Period = period;
            RunOnStart = isRunontart;
            OpenDiagnostics = openDiagnostics;
        }

        public void Start(CancellationToken cancellationToken = default)
        {
            if (Period <= 0)
            {
                throw new Exception("Period 必须赋值");
            }

            lock (_taskTimer)
            {
                _taskTimer.Change(RunOnStart ? 0 : Period, Timeout.Infinite);
                _isRunning = true;
            }
        }

        public void Stop(CancellationToken cancellationToken = default)
        {
            lock (_taskTimer)
            {
                _taskTimer.Change(Timeout.Infinite, Timeout.Infinite);
                while (_performingTasks)
                {
                    Monitor.Wait(_taskTimer);
                }

                _isRunning = false;
            }
        }

        private void TimerCallBack(object state)
        {
            if (ElapsedHandler == null)
            {
                return;
            }

            lock (_taskTimer)
            {
                if (!_isRunning || _performingTasks)
                {
                    return;
                }

                _taskTimer.Change(Timeout.Infinite, Timeout.Infinite);
                _performingTasks = true;
            }

            if (OpenDiagnostics)
            {
                sw = Stopwatch.StartNew();
            }

            try
            {
                ElapsedHandler.Invoke(this, new EventArgs());
            }
            catch(Exception ex)
            {
                ExceptionHandler.Invoke(this, ex);
            }
            finally
            {
                lock (_taskTimer)
                {
                    _performingTasks = false;
                    if (_isRunning)
                    {
                        _taskTimer.Change(Period, Timeout.Infinite);
                    }

                    Monitor.Pulse(_taskTimer);
                }

                if (OpenDiagnostics)
                {
                    sw.Stop();
                    Console.WriteLine(#34;定时器:“{Name}”执行完成,总用时:{sw.ElapsedMilliseconds}毫秒");
                }
            }
        }

        public void Dispose()
        {
            _taskTimer?.Dispose();
        }
    }

测试代码

var time = new AppTimer("test",2000);
time.ElapsedHandler += Time_ElapsedHandler;
time.ExceptionHandler += Time_ExceptionHandler;
time.Start();

private static void Time_ExceptionHandler(object sender, Exception ex)
{
  Console.WriteLine(#34;{DateTime.Now.ToString("HH:mm:ss fff")}发生了异常了:" + ex.Message);
}

private static void Time_ElapsedHandler(object sender, EventArgs e)
{
  var random = new Random();
  var a = random.Next(0, 100);
  if (a % 5 == 0)
  {
    throw new Exception("我是个异常!!!!!");
  }

  if (a % 3 == 0)
  {
    Console.WriteLine(#34;{DateTime.Now.ToString("HH:mm:ss fff")}我是个大任务,预计5s完成");
    Thread.Sleep(5000);
  }
  else
  {
    Console.WriteLine(#34;{DateTime.Now.ToString("HH:mm:ss fff")}我是个小任务,预计0.2s完成");
    Thread.Sleep(200);
  }
}

测试结果

结论

在本文中,我们探讨了在C#中实现自定义定时器的方法,其中包括防止重入和监视执行时间等重要功能。当然代码还可以扩展,比如增加整个应用定时器统计和监控,实时监控定时器运行状态。

Tags:

最近发表
标签列表