下面顯示一下輸出結(jié)果:
是不是感覺上面輸出結(jié)果很奇怪,首先是線程1輸出為1,沒有問題,然后隔了2秒后線程1自增1后輸出為2,這就有問題了,中間為什么還出現(xiàn)了線程2的輸出?更奇怪的是線程2剛開始輸出為1,自增1后盡然變成了3!其實這就是重入所導(dǎo)致的問題。別急,咱們分析一下就知道其中的緣由了。
首先timer啟動計時后,開啟一個線程1執(zhí)行方法,當(dāng)線程1第一次輸出之后,這時線程1休眠了2秒,此時timer并沒有閑著,因為設(shè)置的計時間隔為1秒,當(dāng)在線程1休眠了1秒后,timer又開啟了線程2執(zhí)行方法,線程2才不管線程1是執(zhí)行中還是休眠狀態(tài),所以此時線程2的輸出也為1,因為線程1還在休眠狀態(tài),并沒有自增。然后又隔了1秒,這時發(fā)生同時發(fā)生兩個事件,線程1過了休眠狀態(tài)自增輸出為2,timer同時又開啟一個線程3,線程3輸出的為線程1自增后的值2,又過了1秒,線程2過了休眠狀態(tài),之前的輸出已經(jīng)是2,所以自增后輸出為3,又過了1秒……我都快暈了,大概就是這意思吧,我想表達的就是:一個Timer開啟的線程處理還沒有完成,到了時間,另一Timer還會繼續(xù)進入該方法進行處理。
那怎么解決這個問題呢?解決方案有三種,下面一一道來,適應(yīng)不同的場景,不過還是推薦最后一種,比較安全。
★重入問題解決方案
1、使用lock(Object)的方法來防止重入,表示一個Timer處理正在執(zhí)行,下一個Timer發(fā)生的時候發(fā)現(xiàn)上一個沒有執(zhí)行完就等待執(zhí)行,適用重入很少出現(xiàn)的場景(具體也沒研究過,可能比較占內(nèi)存吧)。
代碼跟上面差不多,在觸發(fā)的方法中加入lock,這樣當(dāng)線程2進入觸發(fā)的方法中,發(fā)現(xiàn)已經(jīng)被鎖,會等待鎖中的代碼處理完在執(zhí)行,代碼如下:
?
1
2
3
4
5
6
7
8
9
10
11 |
private static object locko = new object (); /// /// System.Timers.Timer的回調(diào)方法 /// /// /// private static void TimersTimerHandler( object sender, EventArgs args) { int t = ++num; lock (locko) { 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)); } } |
執(zhí)行結(jié)果:
2、設(shè)置一個標(biāo)志,表示一個Timer處理正在執(zhí)行,下一個Timer發(fā)生的時候發(fā)現(xiàn)上一個沒有執(zhí)行完就放棄(注意這里是放棄,而不是等待哦,看看執(zhí)行結(jié)果就明白啥意思了)執(zhí)行,適用重入經(jīng)常出現(xiàn)的場景。代碼如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 |
private static int inTimer = 0; /// /// System.Timers.Timer的回調(diào)方法 /// /// /// 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; } } |
執(zhí)行結(jié)果:
3、在多線程下給inTimer賦值不夠安全,Interlocked.Exchange提供了一種輕量級的線程安全的給對象賦值的方法(感覺比較高上大,也是比較推薦的一種方法),執(zhí)行結(jié)果與方法2一樣,也是放棄執(zhí)行。Interlocked.Exchange用法參考這里。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 |
private static int inTimer = 0; /// /// System.Timers.Timer的回調(diào)方法 /// /// /// 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); } } |
★總結(jié)
終于碼完字了,真心不容易啊。寫博客是個挺耗精力的事情,真心佩服那些大牛們筆耕不輟,致敬!在這里稍微總結(jié)一下,timer是一個使用挺簡單的類,拿來即用,這里主要總結(jié)了使用timer時重入問題的解決,以前也沒思考過這個問題,解決方案也挺簡單,在這里列出了三種,不知道還有沒有其他的方式。這里的解決方案同時也適用多線程的重入問題。
![]() | ![]() .. 定價:¥133 優(yōu)惠價:¥133.0 更多書籍 |
![]() | ![]() .. 定價:¥124 優(yōu)惠價:¥124.0 更多書籍 |