Timer 計(jì)時(shí)器是在 C# 開(kāi)發(fā)中經(jīng)常用到的,,但是有很多開(kāi)發(fā)人員對(duì)它并不了解,,今天這篇文就具體講解一下 C# 中的計(jì)時(shí)器,。 在 C# 中存在3種常用的 Timer :
System.Windows.Forms.Timer 這個(gè) Timer 是單線程的,也就是說(shuō)只要它運(yùn)行,,其他線程就要等著,。 這個(gè) Timer 有如下特點(diǎn): 完全基于 UI 線程,,定時(shí)器觸發(fā)時(shí),,操作系統(tǒng)把定時(shí)器消息插入線程消息隊(duì)列中,調(diào)用線程執(zhí)行一個(gè)消息泵提取消息,,然后發(fā)送到回調(diào)方法 Tick 中,; 使用 Start 和 Stop 啟動(dòng)和停止 Timer; UI 操作過(guò)長(zhǎng)會(huì)導(dǎo)致 Tick 丟失,; 可以使用委托 Hook Tick 事件,; 精確度不高; 通過(guò)將 Enabled 設(shè)置為 True,,使 Timer 自動(dòng)運(yùn)行,。
從上面的第一個(gè)特點(diǎn)可得知,該 Timer 會(huì)造成 WinForm UI 假死,,因此如果需要定時(shí)處理大量計(jì)算或者大量 IO 操作的任務(wù),,不建議使用該 Timer。接下來(lái)我們看一個(gè)例子體會(huì)一下在IO操作的情況下出現(xiàn)的假死情況,。 我們?cè)?Form 中放入兩個(gè) Button,、一個(gè) Lable 和一個(gè) Timer: private void Button_Click(object sender, EventArgs e) { timer.Interval = 1000; timer.Tick += Timer_Tick; timer.Start(); }
private void Timer_Tick(object sender, EventArgs e) { for (int i = 0; i < 10000; i++) { File.AppendAllText(Directory.GetCurrentDirectory()+'test.txt', i.ToString()); this.label_output.Text = '當(dāng)前操作:插入數(shù)字' + i; } }
我們單擊計(jì)算按鈕,我們會(huì)發(fā)現(xiàn) WinForm 出現(xiàn)了假死(無(wú)法移動(dòng)窗口,、按鈕無(wú)法點(diǎn)擊等),。
System.Timers.Timer 該 Timer 基于服務(wù)器,是為在多線程環(huán)境中用于輔助線程而設(shè)計(jì)的,,可以在線程間移動(dòng)來(lái)處理引發(fā)的 Elapsed 事件,,比上一個(gè)計(jì)時(shí)器更加精確。 該 Timer 有如下特點(diǎn): 通過(guò) Elapsed 設(shè)置回掉處理事件,,且 Elapsed 是運(yùn)行在 ThreadPool 上的,; 通過(guò) Interval 設(shè)置間隔時(shí)間; 當(dāng) AutoReset 設(shè)置為 False 時(shí),,只在到達(dá)第一次時(shí)間間隔后觸發(fā) Elapsed 事件,; 是一個(gè)多線程計(jì)時(shí)器; 無(wú)法直接調(diào)用 WinForm 上的控件,,需要使用委托,; 主要用在 Windows 服務(wù)中。
同樣我們通過(guò)代碼來(lái)看一下該 Timer 計(jì)時(shí)器怎么使用: System.Timers.Timer timersTimer = new System.Timers.Timer(); private void Button_Click(object sender, EventArgs e) { timersTimer.Interval = 1000; timersTimer.Enabled = true; timersTimer.Elapsed += TimersTimer_Elapsed; timersTimer.Start(); }
private void TimersTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { for (int i = 0; i < 10000; i++) { this.BeginInvoke(new Action(() => { this.label_output.Text='當(dāng)前時(shí)間:'+DateTime.Now.ToString('yyyy-MM-dd HH:mm:ss'); }), null); } }
private void Button1_Click(object sender, EventArgs e) { timersTimer.Stop(); }
運(yùn)行上面代碼,,會(huì)發(fā)現(xiàn) WinForm 界面假死的情況消失了,。
System.Threading.Timer 該 Timer 同樣也是一個(gè)多線程的計(jì)時(shí)器,它有如下特點(diǎn): 多線程; 和前兩個(gè)計(jì)時(shí)器相比沒(méi)有 Start 和 Stop 方法,,如果要停止計(jì)時(shí)器,,必須調(diào)用 Dispose 方法來(lái)銷(xiāo)毀 Timer 對(duì)象; 調(diào)用 Dispose 方法后并不能馬上停止所有的計(jì)時(shí)器,,這是因?yàn)殚g隔時(shí)間小于執(zhí)行時(shí)間時(shí)多個(gè)線程運(yùn)行造成的,,多個(gè)線程無(wú)法同時(shí)停止; 是一個(gè)輕量級(jí)的計(jì)時(shí)器,; 所有的參數(shù)全部在構(gòu)造函數(shù)中進(jìn)行了設(shè)置,; 可以設(shè)置啟動(dòng)時(shí)間; 不建議在 WinForm 程序中使用,。
我們來(lái)看一下代碼(在控制臺(tái)應(yīng)用程序中輸入以下代碼): static System.Threading.Timer threadingTimer; static int numSum = 0; static void Main(string[] args) { threadingTimer = new System.Threading.Timer(new System.Threading.TimerCallback(threadingTimer_Elapsed), null, 0, 1000); Console.Read(); } private static void threadingTimer_Elapsed(object state) { for (int i = 0; i < 10000; i++) { numSum++; Console.WriteLine('輸出數(shù)字:'+i); }
if (numSum > 10000) { threadingTimer.Dispose(); Console.WriteLine('結(jié)束'); } }
注意,,當(dāng)我們不再需要多線程 Timer 計(jì)時(shí)器的時(shí)候,我們可以調(diào)用 Dispose 方法釋放所占有的資源,。但是因?yàn)?Timer 計(jì)時(shí)器是按線程池線程來(lái)安排回調(diào)執(zhí)行的,,因此回調(diào)可能發(fā)生在 Dispose 方法的重載被調(diào)用之后,所以我們可以使用可使用 Dispose(WaitHandle) 方法等待所有回掉完成,。
總結(jié) 綜上所屬我們總結(jié)出 C# 中不同 Timer 計(jì)時(shí)器的特點(diǎn)和使用環(huán)境: 作者:朱鋼,,.NET高級(jí)開(kāi)發(fā)工程師,7年一線開(kāi)發(fā)經(jīng)驗(yàn),,參與過(guò)電子政務(wù)系統(tǒng)和AI客服系統(tǒng)的開(kāi)發(fā),,以及互聯(lián)網(wǎng)招聘網(wǎng)站的架構(gòu)設(shè)計(jì),目前就職于北京恒創(chuàng)融慧科技發(fā)展有限公司,。
|