.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為例來介紹一下,推薦大家在項目中用這個計時器。
我們可以看出有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 Fieldsprivate Timer innerTimer;private TimerCallback safeCallback = null!;private TimerCallback originalCallback = null!;private int syncPoint;private ManualResetEvent originalCallbackCompleteEvent = new ManualResetEvent(true);#endregion#region Constructorspublic 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 methodsprivate 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 methodspublic 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?Memberspublic 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);}
}
運行結果如下:
我們看到執行是按照線性執行,沒有并行執行,達到我們預期效果,本質上是將任務調用ThreadPool.QueueUserWorkItem將任務放到線程池中執行!這節就到這里,希望對各位有收獲。