首頁 後端開發 C#.Net教程 詳解C#中Timer的使用與解決重入問題

詳解C#中Timer的使用與解決重入問題

Mar 21, 2017 am 11:50 AM

本文主要介紹了C#中Timer使用及解決重入問題的相關知識。具有很好的參考價值,下面跟著小編一起來看下吧

前言

#打開久違的Live Writer,又已經好久沒寫博客了,真的太懶了。廢話不多說了,直接進入這次部落格的主題--Timer。為什麼要寫這個呢,因為前幾天應朋友之邀,想做個「駭客」小工具,功能挺簡單就是自動取得剪貼簿的內容然後發送郵件,就需要用到Timer來循環取得剪貼簿的內容,但由於到了發送郵件這個功能,使用C#的SmtpClient始終發送不了郵件,以前寫過類似發郵件的功能,當時可以用網易的,現在也不能用了,不知道咋回事,只好作罷。在使用Timer中遇到了之前沒有想過的問題--重入。

介紹

首先簡單介紹timer,這裡所說的timer是指的System.Timers.timer,顧名思義,就是可以在指定的間隔是引發事件。官方介紹在這裡,摘抄如下:

Timer 元件是基於伺服器的計時器,它使您能夠指定在應用程式中引發 Elapsed 事件的周期性間隔。然後可透過處理這個事件來提供常規處理。 例如,假設您有一台關鍵性伺服器,必須每週 7 天、每天 24 小時保持運作。 可以建立一個使用 Timer 的服務,以定期檢查伺服器並確保系統開啟並正在運行。 如果系統不回應,則該服務可以嘗試重新啟動伺服器或通知管理員。 基於伺服器的 Timer 是為在多執行緒環境中用於輔助執行緒而設計的。 伺服器計時器可以在執行緒間移動來處理引發的 Elapsed 事件,這樣就可以比 Windows 計時器更精確地按時引發事件。

如果想了解跟其他的timer有啥區別,可以看這裡,裡面有詳細的介紹,不再多說了(其實我也不知道還有這麼多)。那使用這個計時器有啥好處呢?主要因為它是透過.NET Thread Pool實現的、輕量、計時精確、對應用程式及訊息沒有特別的要求。

使用

下面就簡單介紹一下,這個Timer是怎麼使用的,其實很簡單,我就採用微軟提供的範例來進行測試,直接上程式碼了:

//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);
 }
登入後複製

運行的結果如下,計時蠻準確的:

/*
按任意键退出程序。
触发的事件发生在: 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還會繼續進入這個方法處理。下面示範一下重入問題的產生(可能重現的不是很好,不過也能簡單一下說明問題了):

//用来造成线程同步问题的静态成员
 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进入触发的方法中,发现已经被锁,会等待锁中的代码处理完在执行,代码如下:

private 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)
 {
 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)); } }
登入後複製

执行结果:

2、设置一个标志,表示一个Timer处理正在执行,下一个Timer发生的时候发现上一个没有执行完就放弃(注意这里是放弃,而不是等待哦,看看执行结果就明白啥意思了)执行,适用重入经常出现的场景。代码如下:

 private 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用法参考这里。

private 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); 
 }
 }
登入後複製

执行结果:

总结

终于码完字了,真心不容易啊。写博客是个挺耗精力的事情,真心佩服那些大牛们笔耕不辍,致敬!在这里稍微总结一下,timer是一个使用挺简单的类,拿来即用,这里主要总结了使用timer时重入问题的解决,以前也没思考过这个问题,解决方案也挺简单,在这里列出了三种,不知道还有没有其他的方式。这里的解决方案同时也适用多线程的重入问题。

以上是詳解C#中Timer的使用與解決重入問題的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

使用 C# 的活動目錄 使用 C# 的活動目錄 Sep 03, 2024 pm 03:33 PM

使用 C# 的 Active Directory 指南。在這裡,我們討論 Active Directory 在 C# 中的介紹和工作原理以及語法和範例。

C# 序列化 C# 序列化 Sep 03, 2024 pm 03:30 PM

C# 序列化指南。這裡我們分別討論C#序列化物件的介紹、步驟、工作原理和範例。

C# 中的隨機數產生器 C# 中的隨機數產生器 Sep 03, 2024 pm 03:34 PM

C# 隨機數產生器指南。在這裡,我們討論隨機數產生器的工作原理、偽隨機數和安全數的概念。

C# 資料網格視圖 C# 資料網格視圖 Sep 03, 2024 pm 03:32 PM

C# 資料網格視圖指南。在這裡,我們討論如何從 SQL 資料庫或 Excel 檔案載入和匯出資料網格視圖的範例。

C# 中的階乘 C# 中的階乘 Sep 03, 2024 pm 03:34 PM

C# 階乘指南。這裡我們討論 C# 中階乘的介紹以及不同的範例和程式碼實作。

C# 中的模式 C# 中的模式 Sep 03, 2024 pm 03:33 PM

C# 模式指南。在這裡,我們討論 C# 中模式的介紹和前 3 種類型,以及其範例和程式碼實作。

c#多線程和異步的區別 c#多線程和異步的區別 Apr 03, 2025 pm 02:57 PM

多線程和異步的區別在於,多線程同時執行多個線程,而異步在不阻塞當前線程的情況下執行操作。多線程用於計算密集型任務,而異步用於用戶交互操作。多線程的優勢是提高計算性能,異步的優勢是不阻塞 UI 線程。選擇多線程還是異步取決於任務性質:計算密集型任務使用多線程,與外部資源交互且需要保持 UI 響應的任務使用異步。

C# 中的質數 C# 中的質數 Sep 03, 2024 pm 03:35 PM

C# 質數指南。這裡我們討論c#中素數的介紹和範例以及程式碼實作。

See all articles