.NET中6種定時(shí)器的用法與特點(diǎn)詳解
.NET中至少有6種定時(shí)器,每一種定時(shí)器都有它的用途和特點(diǎn)。根據(jù)定時(shí)器的應(yīng)用場(chǎng)景,可以分為UI相關(guān)的定時(shí)器和UI無關(guān)的定時(shí)器。本文將簡(jiǎn)單介紹這6種定時(shí)器的基本用法和特點(diǎn)。
UI定時(shí)器
.NET中的UI定時(shí)器主要是WinForm、WPF以及WebForm中的定時(shí)器。分別為:
System.Windows.Forms.Timer
System.Windows.Threading.DispatcherTimer
System.Web.UI.Timer
通常情況下,WinForm、WPF中的定時(shí)器是在UI線程上執(zhí)行回調(diào)函數(shù),因此可以直接訪問UI元素。由于WinForm、WPF支持單線程單元模型(Single-Thread Apartment,STA),定時(shí)器間隔事件是在UI線程上觸發(fā),因此,不用擔(dān)心線程安全問題。System.Web.UI.Timer
是通過Javascript定時(shí)器和服務(wù)端異步回調(diào)實(shí)現(xiàn),也是單線程的。
請(qǐng)注意,這里說的是通常情況,后邊介紹System.Windows.Threading.DispatcherTimer
時(shí)會(huì)提到在非UI線程創(chuàng)建DispatcherTimer
時(shí)也無法直接訪問UI元素。
System.Windows.Forms.Timer
System.Windows.Forms.Timer
針對(duì)WinForm應(yīng)用進(jìn)行了優(yōu)化,是只能在WinForm上使用的定時(shí)器。這個(gè)定時(shí)器是針對(duì)單線程環(huán)境設(shè)計(jì)的,是在UI線程上處理定時(shí)任務(wù)。
它要求用戶代碼有可用的UI消息泵,定時(shí)任務(wù)須在UI線程上運(yùn)行,或者跨線程通過Invoke
或者BeginInvoke
封送(marshal)到UI線程上運(yùn)行。其優(yōu)點(diǎn)是使用簡(jiǎn)單,只需通過給Interval
屬性賦值來設(shè)置時(shí)間間隔,并注冊(cè)Tick
事件處理定時(shí)任務(wù)。其缺點(diǎn)是精度不高,精度為55毫秒,也就是Interval
賦值小于55時(shí),也是55毫秒觸發(fā)一次定時(shí)任務(wù)。
public partial class TimerFrom : Form { private System.Windows.Forms.Timer digitalClock; private void TimerFrom_Load(object sender, EventArgs e) { digitalClock = new System.Windows.Forms.Timer();//創(chuàng)建定時(shí)器 digitalClock.Tick += new EventHandler(HandleTime);//注冊(cè)定時(shí)任務(wù)事件 digitalClock.Interval = 1000;//設(shè)置時(shí)間間隔 digitalClock.Enabled = true; digitalClock.Start(); //開啟定時(shí)器 } public void HandleTime(Object myObject, EventArgs myEventArgs) { labelClock.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); } private void frmTimerDemo_FormClosed(object sender, FormClosedEventArgs e) { digitalClock.Stop();//停止定時(shí)器 digitalClock.Dispose(); } }
System.Windows.Threading.DispatcherTimer
System.Windows.Threading.DispatcherTimer
是WPF中的定時(shí)器,它是基于Dispatcher
對(duì)象的(并不是基于UI線程的)。DispatcherTimer
的定時(shí)任務(wù)是像其他操作一樣放在Dispatcher
隊(duì)列上,其執(zhí)行操作時(shí)間依賴于隊(duì)列中其他任務(wù)及其優(yōu)先級(jí),因此,DispatcherTimer
不保證在時(shí)間間隔發(fā)生時(shí)準(zhǔn)確執(zhí)行,只保證不會(huì)在時(shí)間間隔發(fā)生前執(zhí)行。
Dispatcher
為特定線程維護(hù)工作項(xiàng)(操作)的優(yōu)先級(jí)隊(duì)列,在線程上創(chuàng)建Dispatcher
對(duì)象時(shí),它成為唯一可以關(guān)聯(lián)該線程的Dispatcher
對(duì)象,WPF中,DispatcherObject
只能被與之關(guān)聯(lián)的Dispatcher
對(duì)象訪問,也就是非UI線程中無法直接訪問UI元素(WPF中的UI元素都是派生自DispatcherObject
)
此外,DispatcherTimer
不像System.Windows.Forms.Timer
那樣只在UI線程上創(chuàng)建才能觸發(fā)Tick
事件,它在非UI線程下創(chuàng)建也可以觸發(fā)Tick
事件,此時(shí)訪問UI元素也需要通過Invoke
或者BeginInvoke
封送(marshal)到UI線程上運(yùn)行。其優(yōu)點(diǎn)也是簡(jiǎn)單易用,適合在UI線程上執(zhí)行任務(wù)或觸發(fā)事件,缺點(diǎn)是精度不準(zhǔn)確,可能存在延遲。
private void Dt_Tick(object sender, EventArgs e) { Dispatcher.BeginInvoke((Action)delegate () { text1.Text = DateTime.Now.ToString(); }); Console.WriteLine(DateTime.Now.ToString()); } private void Button_Click(object sender, RoutedEventArgs e) { Task.Run(() =>{ DispatcherTimer dt = new DispatcherTimer(); dt.Tick += Dt_Tick; dt.Interval = TimeSpan.FromSeconds(1); dt.Start(); Dispatcher.Run(); }); }
上述代碼中,DispatcherTimer
是非UI線程中創(chuàng)建,定時(shí)任務(wù)中訪問UI元素text1,需要通過Invoke
或者BeginInvoke
封送(marshal)到UI線程上運(yùn)行,而Console.WriteLine
則可以直接運(yùn)行。
System.Web.UI.Timer
System.Web.UI.Timer
是僅適用于.NET Framework
的ASP.NET
組件。通過Javascript定時(shí)器和服務(wù)端異步回調(diào)實(shí)現(xiàn)。每次觸發(fā)定時(shí)器時(shí),只能執(zhí)行一個(gè)異步回調(diào)方法,而其他的異步回調(diào)方法需要等待前一個(gè)異步回調(diào)方法執(zhí)行完畢后才能執(zhí)行。這樣可以保證在任意時(shí)刻只有一個(gè)異步回調(diào)方法在執(zhí)行,避免了多線程并發(fā)執(zhí)行的問題。
UI無關(guān)定時(shí)器
從 .NET 6開始,UI無關(guān)定時(shí)器有三個(gè):
System.Threading.Timer
System.Timers.Timer
System.Threading.PeriodicTimer
(.NET 6+)
System.Threading.Timer
System.Threading.Timer
是最基礎(chǔ)輕量的定時(shí)器,它將定期在線程池線程上執(zhí)行單個(gè)回調(diào)方法。在創(chuàng)建定時(shí)器對(duì)象時(shí)必須指定回調(diào)方法,并且后續(xù)不能修改,同時(shí)也可以指定定時(shí)器回調(diào)開始執(zhí)行的時(shí)間以及時(shí)間間隔。定時(shí)器創(chuàng)建后可以通過Change
方法修改回調(diào)開始執(zhí)行的時(shí)間以及時(shí)間間隔。該定時(shí)器的優(yōu)點(diǎn)是輕量,精度相對(duì)較高,與Windows操作系統(tǒng)時(shí)鐘精度一致,大約15毫秒。但因?yàn)槭腔诰€程池的,所以在任務(wù)執(zhí)行時(shí)間較長(zhǎng)或者線程池過載時(shí),會(huì)出現(xiàn)延遲。其缺點(diǎn)是使用不太方便,定時(shí)器創(chuàng)建后無法修改回調(diào)方法。
var stateTimer = new var autoEvent = new AutoResetEvent(false); Timer(CheckStatus, autoEvent, 1000,250); private int invokeCount=0; public void CheckStatus(Object stateInfo) { AutoResetEvent autoEvent = (AutoResetEvent)stateInfo; Console.WriteLine("{0} Checking status {1,2}.",DateTime.Now.ToString("h:mm:ss.fff"),(++invokeCount).ToString()); if(invokeCount == 10) { invokeCount = 0; autoEvent.Set(); } }
System.Timers.Timer
System.Timers.Timer
在內(nèi)部使用System.Threading.Timer
,并公開了更多的屬性,如AutoReset
, Enabled
或SynchronizingObject
,這些屬性允許配置回調(diào)的執(zhí)行方式。此外,Tick事件允許注冊(cè)多個(gè)處理程序。因此,一個(gè)定時(shí)器可以觸發(fā)多個(gè)處理程序。還可以在計(jì)時(shí)器啟動(dòng)后更改處理程序。與System.Threading.Timer
相似,其優(yōu)點(diǎn)也是精度相對(duì)較高,與Windows操作系統(tǒng)時(shí)鐘精度一致,大約15毫秒。因?yàn)槟J(rèn)(或者SynchronizingObject=null
時(shí))是基于線程池的,所以在任務(wù)執(zhí)行時(shí)間較長(zhǎng)或者線程池過載時(shí),會(huì)出現(xiàn)延遲。但使用要更簡(jiǎn)便一些。
public partial class TimerFrom : Form { private System.Timers.Timer timer; private void TimerFrom_Load(object sender, EventArgs e) { // 支持注冊(cè)多個(gè)處理程序 timer.Elapsed += (sender, e) => { label1.Text = DateTime.Now.ToLongTimeString(); }; timer.Elapsed += (sender, e) => { Console.WriteLine(DateTime.Now.ToLongTimeString()); }; //自定義回調(diào)執(zhí)行的方式(指定對(duì)象所在的線程),SynchronizingObject=null時(shí)在線程池上執(zhí)行 timer.SynchronizingObject = this; timer.AutoReset = true; timer.Start(); } }
本例中將SynchronizingObject
屬性設(shè)置為Form
對(duì)象,因此Elapsed
的處理程序在UI線程上執(zhí)行,可以直接修改label1.Text
,如果SynchronizingObject
屬性為null
,處理程序則是在線程池線程上執(zhí)行,修改label1.Text
時(shí)需要通過Invoke
或者BeginInvoke
封送(marshal)到UI線程上運(yùn)行。
System.Threading.PeriodicTimer
System.Threading.PeriodicTimer
是 .NET 6中引入的定時(shí)器。它能方便地使用異步方式,它沒有Tick
事件,而是提供WaitForNextTickAsync
方法處理定時(shí)任務(wù)。通常是使用While
循環(huán)結(jié)合CancellationToken
一起使用。和CancellationToken
一起用的時(shí)候需要注意,如果CancellationToken
被取消的時(shí)候會(huì)拋出一個(gè)OperationCanceledException
需要考慮自己處理異常。相比之前的定時(shí)器來說,有下面幾個(gè)特點(diǎn):
- 沒有
callback
來綁定事件; - 不會(huì)發(fā)生重入,只允許有一個(gè)消費(fèi)者,不允許同一個(gè)
PeriodicTimer
在不同的地方同時(shí)WaitForNextTickAsync
,不需要自己做排他鎖來實(shí)現(xiàn)不能重入; - 異步化。之前的 timer 的 callback 都是同步的,使用新 timer 可以使用異步方法,避免了編寫 Sync over Async 代碼;
- Dispose 之后,實(shí)例就無法使用,并且 WaitForNextTickAsync 始終返回 false。
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15)); using (var timer = new PeriodicTimer(TimeSpan.FromSeconds(1))) { try { while (await timer.WaitForNextTickAsync(cts.Token)) { await Task.Delay(3000); Console.WriteLine($"ThreadId is {Thread.CurrentThread.ManagedThreadId} --- Time is {DateTime.Now:HH:mm:ss}"); } } catch (OperationCanceledException) { Console.WriteLine("Operation cancelled"); } }
小結(jié)
我們?cè)陂_發(fā)過程中遇到的坑往往不是技術(shù)本身的坑,而是我們?yōu)E用沒有掌握的技術(shù)導(dǎo)致的,在有多種技術(shù)方案可選的時(shí)候,通常只關(guān)注技術(shù)的優(yōu)點(diǎn),忽略了技術(shù)適用場(chǎng)景及其局限性。.NET中幾種定時(shí)器各自都有其適用場(chǎng)景和不足,但都不支持高精度計(jì)時(shí)。了解這些有助于我們?cè)陂_發(fā)過程中選擇合適定時(shí)器,避免遇到問題后被動(dòng)地替換解決方案。
到此這篇關(guān)于.NET中6種定時(shí)器的用法與特點(diǎn)詳解的文章就介紹到這了,更多相關(guān).NET定時(shí)器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MVC4 基礎(chǔ) 枚舉生成 DropDownList 實(shí)用技巧
本篇文章小編為大家介紹,MVC4 基礎(chǔ) 枚舉生成 DropDownList 實(shí)用技巧。需要的朋友參考下2013-04-04.NET Core結(jié)合Nacos實(shí)現(xiàn)配置加解密的方法
當(dāng)我們把應(yīng)用的配置都放到配置中心后,很多人會(huì)想到這樣一個(gè)問題,配置里面有敏感的信息要怎么處理呢?本文就詳細(xì)的介紹了.NET Core Nacos配置加解密,感興趣的可以了解一下2021-06-06ASP.NET―001:GridView綁定List、頁面返回值具體實(shí)現(xiàn)
這篇文章主要介紹了ASP.NET―GridView綁定List、頁面返回值具體實(shí)現(xiàn),需要的朋友可以參考下2014-02-02ASP.NET筆記之 ListView 與 DropDownList的使用
本篇文章小編為大家介紹,ASP.NET筆記之 ListView 與 DropDownList的使用。需要的朋友參考下2013-04-04Visual Studio 2017正式版發(fā)布 Mac版新功能特性有哪些
Visual Studio 2017正式版推出時(shí)間什么時(shí)候?Mac版新功能特性又有哪些?這篇文章就為大家詳細(xì)介紹Visual Studio 2017正式版的最新消息,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03WPF數(shù)據(jù)驅(qū)動(dòng)修改綁定
這篇文章介紹了WPF數(shù)據(jù)驅(qū)動(dòng)修改綁定的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-04-04ASP.NET實(shí)現(xiàn)圖片以二進(jìn)制的形式存入數(shù)據(jù)庫
這篇文章主要介紹了ASP.NET實(shí)現(xiàn)圖片以二進(jìn)制的形式存入數(shù)據(jù)庫,有一定的學(xué)習(xí)借鑒價(jià)值,需要的朋友可以參考下2014-08-08asp.net 根據(jù)漢字的拼音首字母搜索數(shù)據(jù)庫(附 LINQ 調(diào)用方法)
我們經(jīng)常需要使用拼音首字母來檢索數(shù)據(jù)庫,特別是應(yīng)用于醫(yī)院、商店等行業(yè)軟件中。譬如搜索“zgr”就可以搜索所有包含“中國(guó)人”的記錄。那么如果來實(shí)現(xiàn)才能即高效又方便呢?2010-04-04