WPF-23 基于Timer任务调度

.NET的FCL中提供了几个计时器,大多数初学者都不清楚他们有什么不同,那我们这节来剖解一下每个计时器的本质:
1.System.Threading.Timer

如果在一个线程池上执行一个定时的周期性的后台线程任务他是最好的选择,这个类是和线程池相关联的,它告诉线程池(ThreadPool)在指定的时间执行指定的方法

2.System.Timers.Timer

这个类是对System.Threading.Timer类的封装,所以他两本质上是相同,在这里推荐使用System.Threading.Timer计时器.
3.System.Windows.Forms.Timer

这个计时器经常和Window窗体一块使用,而且这个单线程处理的,从放入消息队列,再到提取,执行回调,都是一个线程完成

4.Windows.UI.Xaml.DispatcherTimer

这个类的本质就是System.Windows.Forms.Timer,微软设计目的是被用在Windows Store

5.System.Windows.Threading.DispatcherTimer

这个类和System.Windows.Forms.Timer本质是相同的,但是这个类用在WPF中

我们以System.Threading.Timer为例来介绍一下,推荐大家在项目中用这个计时器。

WPF-23 基于Timer任务调度

我们可以看出有4个构造函数,我们分别讲解一下每个参数的用途
1、callback表示由线程池线程回调的方法,他是一个委托定义如下:
public delegate void TimerCallback(object state);
2、state 参数表示每次调用回调方法时传递的参数,如果没有则为null
3、dueTime表示在调用回调方法之前等待多少毫秒
4、period表示调用callback的时间间隔

我们在做开发的时候会遇到一种场景,当我们一个回调方法执行时间>period 设置的时间,就会导致上一个方法没有执行完,线程池就会新启动一个线程执行相同的方法,这样会产生多个线程同时执行一个方法,如何解决呢?我们可以在初始化Timer的时候给period参数设置为Timeout.Infinite,在回调方法中再次调用Timer.Change(3000,Timeout.Infinite) 并把peroid参数再次设置为Timeout.Infinite,下面代码我们对Timer进行了简单封装:

public class SafeTimer : IDisposable{    #region Fields    private Timer innerTimer;    private TimerCallback safeCallback = null!;    private TimerCallback originalCallback = null!;    private int syncPoint;    private ManualResetEvent originalCallbackCompleteEvent = new ManualResetEvent(true);    #endregion    #region Constructors    public SafeTimer(TimerCallback callback)    {        InitializeCallback(callback);        innerTimer = new Timer(safeCallback);    }    public SafeTimer(TimerCallback callback, object state, long dueTime, long period)    {        InitializeCallback(callback);        innerTimer = new Timer(safeCallback, state, dueTime, period);    }    public SafeTimer(TimerCallback callback, object state, uint dueTime, uint period)    {        InitializeCallback(callback);        innerTimer = new Timer(safeCallback, state, dueTime, period);    }    public SafeTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)    {        InitializeCallback(callback);        innerTimer = new Timer(safeCallback, state, dueTime, period);    }    public SafeTimer(TimerCallback callback, object state, int dueTime, int period)    {        InitializeCallback(callback);        innerTimer = new Timer(safeCallback, state, dueTime, period);    }    #endregion    #region Private methods    private void InitializeCallback(TimerCallback callback)    {        originalCallback = callback;        safeCallback = new TimerCallback(NonReentryCallback);    }    private void NonReentryCallback(object? state)    {        //set syncPoint to 1 if the original value is 0. syncPoint=1 indicates a method is executing.        if (Interlocked.CompareExchange(ref syncPoint, 1, 0) == 0)        {            originalCallbackCompleteEvent.Reset();            try            {                originalCallback(state);            }            catch { }            finally            {                originalCallbackCompleteEvent.Set();                Interlocked.Exchange(ref syncPoint, 0);            }        }    }    #endregion    #region Public methods    public bool Change(long dueTime, long period)    {        return innerTimer.Change(dueTime, period);    }    public bool Change(int dueTime, int period)    {        return innerTimer.Change(dueTime, period);    }    public bool Change(TimeSpan dueTime, TimeSpan period)    {        return innerTimer.Change(dueTime, period);    }    public bool Change(uint dueTime, uint period)    {        return innerTimer.Change(dueTime, period);    }    public void Stop()    {        innerTimer.Change(Timeout.Infinite, Timeout.Infinite);        originalCallbackCompleteEvent.WaitOne();    }    public bool Stop(int milliseconds)    {        innerTimer.Change(Timeout.Infinite, Timeout.Infinite);        return originalCallbackCompleteEvent.WaitOne(milliseconds);    }    #endregion    #region IDisposable Members    public void Dispose()    {        innerTimer.Dispose();    }    #endregion}
我们做个简单的Demo来做个测试:
internal class Program{    private static SafeTimer safeTimer = null!;    static void Main(string[] args)    {        safeTimer = new SafeTimer(WriteLine, string.Empty, 2000, Timeout.Infinite);        Console.ReadLine();    }    public static void WriteLine(object? state)    {        Thread.Sleep(3000);        Console.WriteLine("Hello " +DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss fff"));        safeTimer.Change(2000, Timeout.Infinite);    }}
运行结果如下:

WPF-23 基于Timer任务调度

我们看到执行是按照线性执行,没有并行执行,达到我们预期效果,本质上是将任务调用ThreadPool.QueueUserWorkItem将任务放到线程池中执行!这节就到这里,希望对各位有收获。