public System.Timers.Timer Timer1 = new System.Timers.Timer();
elsapsed事件是在另一個線程中引發的 。如果 elapsed事件中的代碼是耗時的代碼
當事件引發后,調用Timer1.Stop() 方法, 但elapsed 事件線程中的代碼還在執行,直到該次elapse事件中的代碼全執行完。
Timer 控件沒有Elapsed事件吧,System.Timers類才有。
Visual Studio 和 .NET Framework 中包含三個計時器控件:
可添加到“工具箱”中的基於服務器的計時器
始終位於“工具箱”中的基於 Windows 的計時器
可通過編程方式使用的線程計時器
基於 Windows 的計時器針對在 Windows 窗體應用程序中使用而進行了優化。基於服務器的計時器是傳統的計時器為了在服務器環境上運行而優化后的更新版本。線程計時器是一種簡單的、輕量級計時器,它使用回調方法而不是使用事件,並由線程池線程提供支持。
在 Win32 體系結構中有兩種類型的線程:UI 線程和輔助線程。UI 線程絕大多數時間處於空閑狀態,等待消息循環中的消息到來。一旦接收到消息,它們就進行處理並等待下一個消息到來。另外,輔助線程用來執行后台處理而且不使用消息循環。Windows 計時器和基於服務器的計時器在運行時都使用 Interval 屬性。線程計時器的時間間隔在 Timer 構造函數中設置。計時器的設計目的各不相同,它們的線程處理明確地指出了這一點:
Windows 計時器是為單線程環境設計的,其中,UI 線程用於執行處理。Windows 計時器的精度限定為 55 毫秒。這些傳統計時器要求用戶代碼有一個可用的 UI 消息泵,而且總是在同一個線程中操作,或者將調用封送到另一個線程。對於 COM 組件來說,這樣會降低性能。
基於服務器的計時器是為在多線程環境下與輔助線程一起使用而設計的。由於它們使用不同的體系結構,因此基於服務器的計時器可能比 Windows 計時器精確得多。服務器計時器可以在線程之間移動來處理引發的事件。
對消息不在線程上發送的方案中,線程計時器是非常有用的。例如,基於 Windows 的計時器依賴於操作系統計時器的支持,如果不在線程上發送消息,與計時器相關的事件將不會發生。在這種情況下,線程計時器就非常有用。
Windows 計時器位於 System.Windows.Forms 命名空間中,服務器計時器 System.Timers 命名空間中,線程計時器位於 System.Threading 命名空間中。
首先簡單介紹一下timer,這里所說的timer是指的System.Timers.timer,顧名思義,就是可以在指定的間隔是引發事件。官方介紹在這里,摘抄如下:
1
2
|
Timer 組件是基於服務器的計時器,它使您能夠指定在應用程序中引發 Elapsed 事件的周期性間隔。然后可通過處理這個事件來提供常規處理。 例如,假設您有一台關鍵性服務器,必須每周 7 天、每天 24 小時都保持運行。 可以創建一個使用 Timer 的服務,以定期檢查服務器並確保系統開啟並在運行。 如果系統不響應,則該服務可以嘗試重新啟動服務器或通知管理員。
基於服務器的 Timer 是為在多線程環境中用於輔助線程而設計的。 服務器計時器可以在線程間移動來處理引發的 Elapsed 事件,這樣就可以比 Windows 計時器更精確地按時引發事件。
|
如果想了解跟其他的timer有啥區別,可以看這里,里面有詳細的介紹,不再多說了(其實我也不知道還有這么多)。那使用這個計時器有啥好處呢?主要因為它是通過.NET Thread Pool實現的、輕量、計時精確、對應用程序及消息沒有特別的要求。
★使用
下面就簡單介紹一下,這個Timer是怎么使用的,其實很簡單,我就采用微軟提供的示例來進行測試,直接上代碼了:
1234567891011121314151617181920212223242526272829//Timer不要聲明成局部變量,否則會被GC回收
private
static
System.Timers.Timer aTimer;
public
static
void
Main()
{
//實例化Timer類,設置間隔時間為10000毫秒;
aTimer =
new
System.Timers.Timer(10000);
//注冊計時器的事件
aTimer.Elapsed +=
new
ElapsedEventHandler(OnTimedEvent);
//設置時間間隔為2秒(2000毫秒),覆蓋構造函數設置的間隔
aTimer.Interval = 2000;
//設置是執行一次(false)還是一直執行(true),默認為true
aTimer.AutoReset =
true
;
//開始計時
aTimer.Enabled =
true
;
Console.WriteLine(
"按任意鍵退出程序。"
);
Console.ReadLine();
}
//指定Timer觸發的事件
private
static
void
OnTimedEvent(
object
source, ElapsedEventArgs e)
{
Console.WriteLine(
"觸發的事件發生在: {0}"
, e.SignalTime);
}
運行的結果如下,計時蠻准確的:
12345678/*
按任意鍵退出程序。
觸發的事件發生在: 2014/12/26 星期五 23:08:51
觸發的事件發生在: 2014/12/26 星期五 23:08:53
觸發的事件發生在: 2014/12/26 星期五 23:08:55
觸發的事件發生在: 2014/12/26 星期五 23:08:57
觸發的事件發生在: 2014/12/26 星期五 23:08:59
*/
★重入問題重現及分析
什么叫重入呢?這是一個有關多線程編程的概念:程序中,多個線程同時運行時,就可能發生同一個方法被多個進程同時調用的情況。當這個方法中存在一些非線程安全的代碼時,方法重入會導致數據不一致的情況。Timer方法重入是指使用多線程計時器,一個Timer處理還沒有完成,到了時間,另一Timer還會繼續進入該方法進行處理。下面演示一下重入問題的產生(可能重現的不是很好,不過也能簡單一下說明問題了):
123456789101112131415161718192021222324252627282930//用來造成線程同步問題的靜態成員
private
static
int
outPut = 1;
//次數,timer沒調一次方法自增1
private
static
int
num = 0;
private
static
System.Timers.Timer timer =
new
System.Timers.Timer();
public
static
void
Main()
{
timer.Interval = 1000;
timer.Elapsed += TimersTimerHandler;
timer.Start();
Console.WriteLine(
"按任意鍵退出程序。"
);
Console.ReadLine();
}
/// <summary>
/// System.Timers.Timer的回調方法
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private
static
void
TimersTimerHandler(
object
sender, EventArgs args)
{
int
t = ++num;
Console.WriteLine(
string
.Format(
"線程{0}輸出:{1}, 輸出時間:{2}"
, t, outPut.ToString(),DateTime.Now));
System.Threading.Thread.Sleep(2000);
outPut++;
Console.WriteLine(
string
.Format(
"線程{0}自增1后輸出:{1},輸出時間:{2}"
, t, outPut.ToString(),DateTime.Now));
}
下面顯示一下輸出結果:
是不是感覺上面輸出結果很奇怪,首先是線程1輸出為1,沒有問題,然后隔了2秒后線程1自增1后輸出為2,這就有問題了,中間為什么還出現了線程2的輸出?更奇怪的是線程2剛開始輸出為1,自增1后盡然變成了3!其實這就是重入所導致的問題。別急,咱們分析一下就知道其中的緣由了。
首先timer啟動計時后,開啟一個線程1執行方法,當線程1第一次輸出之后,這時線程1休眠了2秒,此時timer並沒有閑着,因為設置的計時間隔為1秒,當在線程1休眠了1秒后,timer又開啟了線程2執行方法,線程2才不管線程1是執行中還是休眠狀態,所以此時線程2的輸出也為1,因為線程1還在休眠狀態,並沒有自增。然后又隔了1秒,這時發生同時發生兩個事件,線程1過了休眠狀態自增輸出為2,timer同時又開啟一個線程3,線程3輸出的為線程1自增后的值2,又過了1秒,線程2過了休眠狀態,之前的輸出已經是2,所以自增后輸出為3,又過了1秒……我都快暈了,大概就是這意思吧,我想表達的就是:一個Timer開啟的線程處理還沒有完成,到了時間,另一Timer還會繼續進入該方法進行處理。
那怎么解決這個問題呢?解決方案有三種,下面一一道來,適應不同的場景,不過還是推薦最后一種,比較安全。
★重入問題解決方案
1、使用lock(Object)的方法來防止重入,表示一個Timer處理正在執行,下一個Timer發生的時候發現上一個沒有執行完就等待執行,適用重入很少出現的場景(具體也沒研究過,可能比較占內存吧)。
代碼跟上面差不多,在觸發的方法中加入lock,這樣當線程2進入觸發的方法中,發現已經被鎖,會等待鎖中的代碼處理完在執行,代碼如下:
12345678private
static
object
locko =
new
object
();
/// <summary>
/// System.Timers.Timer的回調方法
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private
static
void
TimersTimerHandler(
object
sender, EventArgs args)
{
1int
t = ++num; <br>
lock
(locko)
1234567<em id=
"__mceDel"
> {
Console.WriteLine(
string
.Format(
"線程{0}輸出:{1}, 輸出時間:{2}"
, t, outPut.ToString(), DateTime.Now));
System.Threading.Thread.Sleep(2000);
outPut++;
Console.WriteLine(
string
.Format(
"線程{0}自增1后輸出:{1},輸出時間:{2}"
, t, outPut.ToString(), DateTime.Now));
}
}</em>
執行結果:
2、設置一個標志,表示一個Timer處理正在執行,下一個Timer發生的時候發現上一個沒有執行完就放棄(注意這里是放棄,而不是等待哦,看看執行結果就明白啥意思了)執行,適用重入經常出現的場景。代碼如下:
12345678910111213141516171819private
static
int
inTimer = 0;
/// <summary>
/// System.Timers.Timer的回調方法
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private
static
void
TimersTimerHandler(
object
sender, EventArgs args)
{
int
t = ++num;
if
(inTimer == 0)
{
inTimer = 1;
Console.WriteLine(
string
.Format(
"線程{0}輸出:{1}, 輸出時間:{2}"
, t, outPut.ToString(), DateTime.Now));
System.Threading.Thread.Sleep(2000);
outPut++;
Console.WriteLine(
string
.Format(
"線程{0}自增1后輸出:{1},輸出時間:{2}"
, t, outPut.ToString(), DateTime.Now));
inTimer = 0;
}
}
執行結果:
3、在多線程下給inTimer賦值不夠安全,Interlocked.Exchange提供了一種輕量級的線程安全的給對象賦值的方法(感覺比較高上大,也是比較推薦的一種方法),執行結果與方法2一樣,也是放棄執行。Interlocked.Exchange用法參考這里。
123456789101112131415161718private
static
int
inTimer = 0;
/// <summary>
/// System.Timers.Timer的回調方法
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private
static
void
TimersTimerHandler(
object
sender, EventArgs args)
{
int
t = ++num;
if
(Interlocked.Exchange(
ref
inTimer, 1) == 0)
{
Console.WriteLine(
string
.Format(
"線程{0}輸出:{1}, 輸出時間:{2}"
, t, outPut.ToString(), DateTime.Now));
System.Threading.Thread.Sleep(2000);
outPut++;
Console.WriteLine(
string
.Format(
"線程{0}自增1后輸出:{1},輸出時間:{2}"
, t, outPut.ToString(), DateTime.Now));
Interlocked.Exchange(
ref
inTimer, 0);
}
}
執行結果:
1<img style=
"BACKGROUND-IMAGE: none; BORDER-BOTTOM: 0px; BORDER-LEFT: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; DISPLAY: inline; BORDER-TOP: 0px; BORDER-RIGHT: 0px; PADDING-TOP: 0px"
title=
"image"
border=
"0"
alt=
"image"
src=
"/upload/201412/30/270111349058497.png"
width=
"640"
height=
"179"
>
★總結
從別處拷貝過來,留存自己學習