程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> .NET中的三種Timer的區別和用法,.net三種timer用法

.NET中的三種Timer的區別和用法,.net三種timer用法

編輯:C#入門知識

.NET中的三種Timer的區別和用法,.net三種timer用法


最近正好做一個WEB中定期執行的程序,而.NET中有3個不同的定時器。所以正好研究研究。這3個定時器分別是: 

//1.實現按用戶定義的時間間隔引發事件的計時器。此計時器最宜用於 Windows 窗體應用程序中,並且必須在窗口中使用。 
System.Windows.Forms.Timer 

// 2.提供以指定的時間間隔執行方法的機制。無法繼承此類。 
System.Threading.Timer 

//3.在應用程序中生成定期事件。 
System.Timers.Timer 

這三個定時器位於不同的命名空間內,上面大概介紹了3個定時器的用途,其中第一個是只能在Windows窗體中使用的控件。在.NET1.1裡面,第3個System.Timers.Timer,也是可以拖拽使用,而.NET2.0開始取消了,只能手動編寫代碼。而後2個沒有限制制。下面通過具體的列子來看3個Timer的使用和區別,網上談的很多,但基本都沒有代碼。 

一 System.Windows.Forms.Timer 

#region System.Windows.Forms.Timer 
public partial class Form1 : Form 

public Form1() 

InitializeComponent(); 


int num = 0; 

private void Form_Timer_Tick(object sender, EventArgs e) 

label1.Text = (++num).ToString(); 
Thread.Sleep(3000); 


private void button1_Click(object sender, EventArgs e) 

Form_Timer.Start(); 


private void button2_Click(object sender, EventArgs e) 

Form_Timer.Stop(); 


#endregion 
上面這個是一個很簡單的功能,在Form窗體上拖了一個System.Windows.Forms.Timer控件名字為Form_Timer,在屬性窗中把Enable屬性設置為Ture,Interval是定時器的間隔時間。雙擊這個控件就可以看到 Form_Timer_Tick方法。在這個方法中,我們讓她不停的加一個數字並顯示在窗體上,2個按鈕提供了對計時器的控制功能。 

執行的時候你去點擊其他窗體在回來,你會發現我們的窗體失去響應了。因為我們這裡使用Thread.Sleep(3000);讓當前線程掛起,而UI失去相應,說明了這裡執行時候采用的是單線程。也就是執行定時器的線程就是UI線程。 

Timer 用於以用戶定義的事件間隔觸發事件。Windows 計時器是為單線程環境設計的,其中,UI 線程用於執行處理。它要求用戶代碼有一個可用的 UI 消息泵,而且總是在同一個線程中操作,或者將調用封送到另一個線程。 

在Timer內部定義的了一個Tick事件,我們前面雙擊這個控件時實際是增加了一行代碼 

this.Form_Timer.Tick += new System.EventHandler(this.Form_Timer_Tick); 
這個應該明白,不明白的可以看我BLOG中有關委托和事件的文章。然後Windows將這個定時器與調用線程關聯(UI線程)。當定時器觸發時,Windows把一個定時器消息插入到線程消息隊列中。調用線程執行一個消息泵提取消息,然後發送到回調方法中(這裡的Form_Timer_Tick方法)。而這些都是單線程進行了,所以在執行回調方法時UI會假死。所以使用這個控件不宜執行計算受限或IO受限的代碼,因為這樣容易導致界面假死,而應該使用多線程調用的Timer。另外要注意的是這個控件時間精度不高,精度限定為 55 毫秒。我們把Interval設置為20ms,然後在start和stop方法中記錄當前時,並計算出運行時間: 





從上面圖可以看到程序執行了7.8S也就是 7800ms,而間隔時間是20ms,也就是最後顯示數字應該是390左右,但只有250,顯然是不准確的,不過按MSDN說的55ms的精度,7800ms應該只執行了140多次或更少。不知道這裡是不是理解有問題。 

二 System.Timers.Timer 

接下來就看下另一個Timer,我們用他來改寫上面的程序 

#region System.Windows.Forms.Timer 
public partial class Form1 : Form 

public Form1() 

InitializeComponent(); 


int num = 0; 
DateTime time1 = new DateTime(); 
DateTime time2 = new DateTime(); 
//定義Timer 
System.Timers.Timer Timers_Timer = new System.Timers.Timer(); 

private void button1_Click(object sender, EventArgs e) 

//手動設置Timer,開始執行 
Timers_Timer.Interval = 20; 
Timers_Timer.Enabled = true; 
Timers_Timer.Elapsed += new System.Timers.ElapsedEventHandler(Timers_Timer_Elapsed); 
time1 = DateTime.Now; 


void Timers_Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) 

label1.Text = Convert.ToString((++num)); //顯示到lable 
Thread.Sleep(3000); 


private void button2_Click(object sender, EventArgs e) 

//停止執行 
Timers_Timer.Enabled = false; 
time2 = DateTime.Now; 
MessageBox.Show(Convert.ToString(time2-time1)); 


#endregion 
我們可以看到這個代碼和前面使用Form.Timer的基本相同,不同的是我們是手動定義的對象,而不是通過拉控件。他也有Interval ,Enabled 等屬性,作用和第一是一樣的。不同的是他的事件名為Elapsed ,但是和上面的Tick一樣,綁定一個委托的方法。只是這裡我們是手動完成的。另外不同之處是Form.Timer我們可以用Stop和Start方法控制,而這裡是通過Enable屬性控制。但實際上也可以用Stop和Start方法,內部也是通過他自己的Enable來控制的。 

最大的不同就是上面的代碼在調試時會報錯,提示你"線程間操作無效: 從不是創建控件“label1”的線程訪問它。"但如果你不調試直接運行是OK的,而且運行時你去拖動窗體會發現沒有出現假死。從這裡我們就可以知道這裡的Timer的創建線程和執行線程不是同一個線程。也就是使用了多線程。Timer的創建線程是UI線程,而執行線程是TheardPool中的線程,所以不會假死,但調試的時候會報錯,因為非控件的創建線程不能操作控件。但你可以直接運行,這裡是VS05做了手腳。解決辦法很多,用delegate.BeginInvoke()等等。這裡介紹特有的一種方法,設置Timer的SynchronizingObject屬性,Timers_Timer.SynchronizingObject = label1;這樣的話,我們的話,調試運行時就不會報錯了,但是設置了這個屬性Timer就編程單線程調用了,就基本和第一個完全一樣了。

Timer 是為在多線程環境中用於輔助線程而設計的。服務器計時器可以在線程間移動來處理引發的 Elapsed 事件,這樣就可以比 Windows 計時器更精確地按時引發事件。Elapsed 事件在 ThreadPool 線程上引發。如果 Elapsed 事件的處理時間比 Interval 長,在另一個 ThreadPool 線程上將會再次引發此事件。因此,事件處理程序應當是可重入的。 

另外和前面不同的現象是每次加1後並沒有停止3秒在顯示。而是繼續顯示,只是速度稍慢。因為我們設置間隔為20ms,而執行時間為3s,所以會在20ms後在另一個線程中繼續執行,而當前線程被掛起而已。關於計時器的精度,取消3s的掛起,發現結果和第一個基本一致。 

三 System.Threading.Timer 

繼續用這個對象改造程序。 

#region System.Windows.Forms.Timer 
public partial class Form1 : Form 

public Form1() 

InitializeComponent(); 


int num = 0; 
DateTime time1 = new DateTime(); 
DateTime time2 = new DateTime(); 
System.Threading.Timer Thread_Time; 

private void button1_Click(object sender, EventArgs e) 

//啟動 
Thread_Time = new System.Threading.Timer(Thread_Timer_Method,null,0,20); 
time1 = DateTime.Now; 



void Thread_Timer_Method(object o) 

label1.Text = Convert.ToString((++num)); 
System.Threading.Thread.Sleep(3000); 


private void button2_Click(object sender, EventArgs e) 

//停止 
Thread_Time.Dispose(); 
time2 = DateTime.Now; 
MessageBox.Show(Convert.ToString(time2-time1)); 


#endregion 
用Threading.Timer時的方法,和前面就不太相同了,所以的參數全部在構造函數中進行了設置,而且可以設置啟動時間。而且沒有提供start和stop方法來控制計時器。而且是以一種回調方法的方式實現,而不是通過事件來實現的。他們之間還是有區別的。 

我們只有銷毀掉對象來停止他。當你運行時,你會發現他和前面的Timers.Timer一樣,是多線程的,主要表現在不會假死,調試運行報錯。但跟讓你奇怪的是,我們的代碼竟然無法讓她停止下來。調用了Dispose方法沒有用。問題在那?然後有進行了測試,修改了間隔時間為100,200,500,1000,3000,4000。這幾種情況。發現當間隔為500ms以上是基本馬上就停止了。而間隔時間相對執行時間越短,繼續執行的時間越長。這應該是在間隔時間小於執行時間時多個線程運行造成的。因為所有的線程不是同時停止的。間隔越短,線程越多,所以執行次數越多。 



最後來看下這個對象另外一個特殊的地方。 

static void Main() 

Timer t = new Timer(Test,null,0,1000); 
Console.ReadLine(); 


public static void Test(object o) 

Console.WriteLine("nihao"); 
GC.Collect(); 

這段代碼會輸出什麼結果呢?默認情況他只輸出一次,就停止了。為什麼呢?根據上面說的,當定義對象t,執行代碼後,進行了強制垃圾回收,因為t在Main中沒有其他引用,所以被回收掉了。但是如果我們吧編譯器的”優化“項取消掉,在看看情況。程序進然一直在輸出。為什麼執行垃圾回收卻沒有被回收呢?因為這個禁用優化選項,t的聲明周期被擴展到了方法結束。所以一直執行。 

因為編譯器默認是優化的,所以我們必須保證Timer對象一直被引用,而避免被垃圾回收。所以我們可以在編譯器打開優化的情況下,在Main函數最後加上t=null保證回收前被引用,但你發現,這樣是沒用的。因為JIT編譯器優化後會吧t=null直接刪除,所以我們用t.Dispose(),就可以達到目的。在我們進行垃圾回收時,CLR發現t還有被引用,還沒執行Dispose所以不會被回收。是以Threading.Timer有時候會出現運行一次就停止或者是銷毀了還在運行的情況,而且和編譯器優化也有關,所以使用時要注意。 

最後看下MSDN的描述: 只要在使用 Timer,就必須保留對它的引用。對於任何托管對象,如果沒有對 Timer 的引用,計時器會被垃圾回收。即使 Timer 仍處在活動狀態,也會被回收。當不再需要計時器時,請使用 Dispose 方法釋放計時器持有的資源。如果希望在計時器被釋放時接收到信號,請使用接受 WaitHandle 的 Dispose(WaitHandle) 方法重載。計時器已被釋放後,WaitHandle 便終止。 



總結: 

System.Threading.Timer 是一個簡單的輕量計時器,它使用回調方法並由線程池線程提供服務。不建議將其用於 Windows 窗體,因為其回調不在用戶界面線程上進行。System.Windows.Forms.Timer 是用於 Windows 窗體的更佳選擇。要獲取基於服務器的計時器功能,可以考慮使用 System.Timers.Timer,它可以引發事件並具有其他功能。 

在《CLR Via C#》中講多線程時有提到這3個計時器,但作者說System.Timers.Timer是對System.Threading.Timer的報裝,不推薦使用,但是在我的WEB項目中的Application_Start中我還是使用的這個而不是Threading.Timer,因為使用Threading.Timer時只執行了一次就不在執行了。 

對於計時器在B/S結構中的使用就復雜一些,一般我們把計時器放在Application_OnStart中,這樣全局維護一個計時器,可以進行定期備份數據庫,定期維護用戶等操作,而且方法寫作靜態的,以免被垃圾回收。而不建議在一般的aspx頁面中使用,因為服務器端的定時器對用戶這樣意義不大,完全可以使用JS代替。而且這個頁面的每個請求都可能引入一個新的定時器,導致系統崩潰。另外,定時器是ASP.NET進程,IIS有關,所以對用重要的執行任務,還是建議寫成服務或獨立程序放在服務器上執行好了。 


C# 三種timer 的不同

在C#裡關於定時器類就有3個
1.定義在System.Windows.Forms裡
2.定義在System.Threading.Timer類裡
3.定義在System.Timers.Timer類裡
System.Windows.Forms.Timer是應用於WinForm中的,它是通過Windows消息機制實現的,類似於VB或Delphi中的Timer控件,內部使用API SetTimer實現的。它的主要缺點是計時不精確,而且必須有消息循環,Console Application(控制台應用程序)無法使用。

System.Timers.Timer和System.Threading.Timer非常類似,它們是通過.NET Thread Pool實現的,輕量,計時精確,對應用程序、消息沒有特別的要求。System.Timers.Timer還可以應用於WinForm,完全取代上面的Timer控件。它們的缺點是不支持直接的拖放,需要手工編碼。

例:
使用System.Timers.Timer類
System.Timers.Timer t = new System.Timers.Timer(10000);//實例化Timer類,設置間隔時間為10000毫秒;
t.Elapsed += new System.Timers.ElapsedEventHandler(theout);//到達時間的時候執行事件;
t.AutoReset = true;//設置是執行一次(false)還是一直執行(true);
t.Enabled = true;//是否執行System.Timers.Timer.Elapsed事件;

public void theout(object source, System.Timers.ElapsedEventArgs e)
{
MessageBox.Show("OK!");
}
 

NET中三個Timer的用處!

其實這沒什麼好說的,翻開MSDN看就行了。

System.Windows.Forms:實現按用戶定義的時間間隔引發事件的計時器。此計時器最宜用於 Windows 窗體應用程序中,並且必須在窗口中使用。
Windows 窗體 Timer 組件是單線程組件,精度限定為 55 毫秒。如果您需要更高精度的多線程計時器,請使用 System.Timers 命名空間中的 Timer 類。

System.Threading.Timer 是一個簡單的輕量計時器,它使用回調方法並由線程池線程提供服務。不建議將其用於 Windows 窗體,因為其回調不在用戶界面線程上進行。System.Windows.Forms.Timer 是用於 Windows 窗體的更佳選擇。要獲取基於服務器的計時器功能,可以考慮使用 System.Timers.Timer,它可以引發事件並具有其他功能。

System.Timers:Timer 組件是基於服務器的計時器,它使您能夠指定在應用程序中引發 Elapsed 事件的周期性間隔。然後可以操控此事件以提供定期處理。在應用程序中生成定期事件。基於服務器的 Timer 是為在多線程環境中用於輔助線程而設計的。服務器計時器可以在線程間移動來處理引發的 Elapsed 事件,這樣就可以比 Windows 計時器更精確地按時引發事件。
 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved