亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C# > 正文

C#中的多線程超時處理實踐方案

2019-10-29 21:06:28
字體:
來源:轉載
供稿:網友

最近我正在處理C#中關于timeout行為的一些bug。解決方案非常有意思,所以我在這里分享給廣大博友們。

我要處理的是下面這些情況:

  • 我們做了一個應用程序,程序中有這么一個模塊,它的功能向用戶顯示一個消息對話框,15秒后再自動關閉該對話框。但是,如果用戶手動關閉對話框,則在timeout時我們無需做任何處理。
  • 程序中有一個漫長的執行操作。如果該操作持續5秒鐘以上,那么請終止這個操作。
  • 我們的的應用程序中有執行時間未知的操作。當執行時間過長時,我們需要顯示一個“進行中”彈出窗口來提示用戶耐心等待。我們無法預估這次操作會持續多久,但一般情況下會持續不到一秒。為了避免彈出窗口一閃而過,我們只想要在1秒后顯示這個彈出窗口。反之,如果在1秒內操作完成,則不需要顯示這個彈出窗口。

這些問題是相似的。在超時之后,我們必須執行X操作,除非Y在那個時候發生。

為了找到解決這些問題的辦法,我在試驗過程中創建了一個類:

public class OperationHandler{ private IOperation _operation;  public OperationHandler(IOperation operation) { _operation = operation; }  public void StartWithTimeout(int timeoutMillis) { //在超時后需要調用 "_operation.DoOperation()"  }  public void StopOperationIfNotStartedYet() { //在超時期間需要停止"DoOperation"  }}

我的操作類:

public class MyOperation : IOperation{ public void DoOperation() { Console.WriteLine("Operation started"); }}public class MyOperation : IOperation{ public void DoOperation() { Console.WriteLine("Operation started"); }}

我的測試程序:

static void Main(string[] args){ var op = new MyOperation(); var handler = new OperationHandler(op); Console.WriteLine("Starting with timeout of 5 seconds"); handler.StartWithTimeout(5 * 1000); Thread.Sleep(6 * 1000);  Console.WriteLine("Starting with timeout of 5 but cancelling after 2 seconds"); handler.StartWithTimeout(5 * 1000); Thread.Sleep(2 * 1000); handler.StopOperationIfNotStartedYet();  Thread.Sleep(4 * 1000); Console.WriteLine("Finished..."); Console.ReadLine();}

結果應該是:

Starting with timeout of 5 secondsOperation startedStarting with timeout of 5 but cancelling after 2 secondsFinished...

現在我們可以開始試驗了!

解決方案1:在另一個線程上休眠

我最初的計劃是在另一個不同的線程上休眠,同時用一個布爾值來標記Stop是否被調用。

public class OperationHandler{ private IOperation _operation; private bool _stopCalled; public OperationHandler(IOperation operation) { _operation = operation; } public void StartWithTimeout(int timeoutMillis) { Task.Factory.StartNew(() => {  _stopCalled = false;  Thread.Sleep(timeoutMillis);  if (!_stopCalled)  _operation.DoOperation(); }); } public void StopOperationIfNotStartedYet() { _stopCalled = true; }}

針對正常的線程執行步驟,這段代碼運行過程并沒有出現問題,但是總是感覺有些別扭。仔細探究后,我發現其中有一些貓膩。首先,在超時期間,有一個線程從線程池中取出后什么都沒做,顯然這個線程是被浪費了。其次,如果程序停止執行了,線程會繼續休眠直到超時結束,浪費了CPU時間。

但是這些并不是我們這段代碼最糟糕的事情,實際上我們的程序實還存在一個明顯的bug:

如果我們設置10秒的超時時間,開始操作后,2秒停止,然后在2秒內再次開始。

當第二次啟動時,我們的_stopCalled標志將變成false。然后,當我們的第一個Thread.Sleep()完成時,即使我們取消它,它也會調用DoOperation。

之后,第二個Thread.Sleep()完成,并將第二次調用DoOperation。結果導致DoOperation被調用兩次,這顯然不是我們所期望的。

如果你每分鐘有100次這樣的超時,我將很難捕捉到這種錯誤。

當StopOperationIfNotStartedYet被調用時,我們需要某種方式來取消DoOperation的調用。

如果我們嘗試使用計時器呢?

解決方案2:使用計時器

.NET中有三種不同類型的記時器,分別是:

  • System.Windows.Forms命名空間下的Timer控件,它直接繼承自Componet。
  • System.Timers命名空間下的Timer類。
  • System.Threading.Timer類。

這三種計時器中,System.Threading.Timer足以滿足我們的需求。這里是使用Timer的代碼:

public class OperationHandler{ private IOperation _operation; private Timer _timer; public OperationHandler(IOperation operation) { _operation = operation; } public void StartWithTimeout(int timeoutMillis) { if (_timer != null)  return; _timer = new Timer(  state =>  {  _operation.DoOperation();  DisposeOfTimer();  }, null, timeoutMillis, timeoutMillis); }  public void StopOperationIfNotStartedYet() { DisposeOfTimer(); } private void DisposeOfTimer() { if (_timer == null)  return; var temp = _timer; _timer = null; temp.Dispose(); }}

執行結果如下:

Starting with timeout of 5 secondsOperation startedStarting with timeout of 5 but cancelling after 2 secondsFinished...

現在當我們停止操作時,定時器被丟棄,這樣就避免了再次執行操作。這已經實現了我們最初的想法,當然還有另一種方式來處理這個問題。

解決方案3:ManualResetEvent或AutoResetEvent

ManualResetEvent/AutoResetEvent的字面意思是手動或自動重置事件。AutoResetEvent和ManualResetEvent是幫助您處理多線程通信的類。 基本思想是一個線程可以一直等待,知道另一個線程完成某個操作, 然后等待的線程可以“釋放”并繼續運行。

ManualResetEvent類和AutoResetEvent類請參閱MSDN:

ManualResetEvent類:https://msdn.microsoft.com/zh-cn/library/system.threading.manualresetevent.aspx
AutoResetEvent類:https://msdn.microsoft.com/zh-cn/library/system.threading.autoresetevent.aspx

言歸正傳,在本例中,直到手動重置事件信號出現,mre.WaitOne()會一直等待。 mre.Set()將標記重置事件信號。 ManualResetEvent將釋放當前正在等待的所有線程。AutoResetEvent將只釋放一個等待的線程,并立即變為無信號。WaitOne()也可以接受超時作為參數。 如果Set()在超時期間未被調用,則線程被釋放并且WaitOne()返回False。

以下是此功能的實現代碼:

public class OperationHandler{ private IOperation _operation; private ManualResetEvent _mre = new ManualResetEvent(false); public OperationHandler(IOperation operation) { _operation = operation; } public void StartWithTimeout(int timeoutMillis) { _mre.Reset(); Task.Factory.StartNew(() => {  bool wasStopped = _mre.WaitOne(timeoutMillis);  if (!wasStopped)  _operation.DoOperation(); }); }  public void StopOperationIfNotStartedYet() { _mre.Set(); }}

執行結果:

Starting with timeout of 5 secondsOperation startedStarting with timeout of 5 but cancelling after 2 secondsFinished...

我個人非常傾向于這個解決方案,它比我們使用Timer的解決方案更干凈簡潔。
對于我們提出的簡單功能,ManualResetEvent和Timer解決方案都可以正常工作。 現在讓我們增加點挑戰性。

新的改進需求

假設我們現在可以連續多次調用StartWithTimeout(),而不是等待第一個超時完成后調用。

但是這里的預期行為是什么?實際上存在以下幾種可能性:

  1. 在以前的StartWithTimeout超時期間調用StartWithTimeout時:忽略第二次啟動。
  2. 在以前的StartWithTimeout超時期間調用StartWithTimeout時:停止初始話Start并使用新的StartWithTimeout。
  3. 在以前的StartWithTimeout超時期間調用StartWithTimeout時:在兩個啟動中調用DoOperation。 在StopOperationIfNotStartedYet中停止所有尚未開始的操作(在超時時間內)。
  4. 在以前的StartWithTimeout超時期間調用StartWithTimeout時:在兩個啟動中調用DoOperation。 在StopOperationIfNotStartedYet停止一個尚未開始的隨機操作。

可能性1可以通過Timer和ManualResetEvent可以輕松實現。 事實上,我們已經在我們的Timer解決方案中涉及到了這個。

public void StartWithTimeout(int timeoutMillis){ if (_timer != null) return; ...  public void StartWithTimeout(int timeoutMillis) { if (_timer != null) return; ...}

可能性2也可以很容易地實現。 這個地方請允許我賣個萌,代碼自己寫哈^_^

可能性3不可能通過使用Timer來實現。 我們將需要有一個定時器的集合。 一旦停止操作,我們需要檢查并處理定時器集合中的所有子項。 這種方法是可行的,但通過ManualResetEvent我們可以非常簡潔和輕松的實現這一點!

可能性4跟可能性3相似,可以通過定時器的集合來實現。

可能性3:使用單個ManualResetEvent停止所有操作

讓我們了解一下這里面遇到的難點:

假設我們調用StartWithTimeout 10秒超時。

1秒后,我們再次調用另一個StartWithTimeout,超時時間為10秒。

再過1秒后,我們再次調用另一個StartWithTimeout,超時時間為10秒。

預期的行為是這3個操作會依次10秒、11秒和12秒后啟動。

如果5秒后我們會調用Stop(),那么預期的行為就是所有正在等待的操作都會停止, 后續的操作也無法進行。

我稍微改變下Program.cs,以便能夠測試這個操作過程。 這是新的代碼:

class Program{ static void Main(string[] args) { var op = new MyOperation(); var handler = new OperationHandler(op); Console.WriteLine("Starting with timeout of 10 seconds, 3 times"); handler.StartWithTimeout(10 * 1000); Thread.Sleep(1000); handler.StartWithTimeout(10 * 1000); Thread.Sleep(1000); handler.StartWithTimeout(10 * 1000); Thread.Sleep(13 * 1000); Console.WriteLine("Starting with timeout of 10 seconds 3 times, but cancelling after 5 seconds"); handler.StartWithTimeout(10 * 1000); Thread.Sleep(1000); handler.StartWithTimeout(10 * 1000); Thread.Sleep(1000); handler.StartWithTimeout(10 * 1000); Thread.Sleep(5 * 1000); handler.StopOperationIfNotStartedYet(); Thread.Sleep(8 * 1000); Console.WriteLine("Finished..."); Console.ReadLine(); }}

下面就是使用ManualResetEvent的解決方案:

public class OperationHandler{ private IOperation _operation; private ManualResetEvent _mre = new ManualResetEvent(false); public OperationHandler(IOperation operation) { _operation = operation; } public void StartWithTimeout(int timeoutMillis) { Task.Factory.StartNew(() => {  bool wasStopped = _mre.WaitOne(timeoutMillis);  if (!wasStopped)  _operation.DoOperation(); }); }  public void StopOperationIfNotStartedYet() { Task.Factory.StartNew(() => {  _mre.Set();  Thread.Sleep(10);//This is necessary because if calling Reset() immediately, not all waiting threads will 'proceed'  _mre.Reset(); }); }}

輸出結果跟預想的一樣:

Starting with timeout of 10 seconds, 3 timesOperation startedOperation startedOperation startedStarting with timeout of 10 seconds 3 times, but cancelling after 5 secondsFinished...

很開森對不對?

當我檢查這段代碼時,我發現Thread.Sleep(10)是必不可少的,這顯然超出了我的意料。 如果沒有它,除3個等待中的線程之外,只有1-2個線程正在進行。 很明顯的是,因為Reset()發生得太快,第三個線程將停留在WaitOne()上。

可能性4:單個AutoResetEvent停止一個隨機操作

假設我們調用StartWithTimeout 10秒超時。1秒后,我們再次調用另一個StartWithTimeout,超時時間為10秒。再過1秒后,我們再次調用另一個StartWithTimeout,超時時間為10秒。然后我們調用StopOperationIfNotStartedYet()。

目前有3個操作超時,等待啟動。 預期的行為是其中一個被停止, 其他2個操作應該能夠正常啟動。

我們的Program.cs可以像以前一樣保持不變。 OperationHandler做了一些調整:

public class OperationHandler{ private IOperation _operation; private AutoResetEvent _are = new AutoResetEvent(false); public OperationHandler(IOperation operation) { _operation = operation; } public void StartWithTimeout(int timeoutMillis) { _are.Reset(); Task.Factory.StartNew(() => {  bool wasStopped = _are.WaitOne(timeoutMillis);  if (!wasStopped)  _operation.DoOperation(); }); }  public void StopOperationIfNotStartedYet() { _are.Set(); }}

執行結果是:

Starting with timeout of 10 seconds, 3 timesOperation startedOperation startedOperation startedStarting with timeout of 10 seconds 3 times, but cancelling after 5 secondsOperation startedOperation startedFinished...

結語

在處理線程通信時,超時后繼續執行某些操作是常見的應用。我們嘗試了一些很好的解決方案。一些解決方案可能看起來不錯,甚至可以在特定的流程下工作,但是也有可能在代碼中隱藏著致命的bug。當這種情況發生時,我們應對時需要特別小心。

AutoResetEvent和ManualResetEvent是非常強大的類,我在處理線程通信時一直使用它們。這兩個類非常實用。正在跟線程通信打交道的朋友們,快把它們加入到項目里面吧!

總結

以上所述是小編給大家介紹的C#中的多線程超時處理實踐方案,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對VEVB武林網網站的支持!


注:相關教程知識閱讀請移步到c#教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品视频xxx| 亚洲japanese制服美女| 欧美在线视频观看| 国产亚洲欧美另类中文| 国产男人精品视频| 亚洲国产高清福利视频| 日韩经典中文字幕| 亚洲精品自拍偷拍| 久久国产天堂福利天堂| 亚洲视频日韩精品| 91sao在线观看国产| 黑人巨大精品欧美一区二区三区| 久久精品国产亚洲精品| 欧美理论电影网| 久久久精品久久| www欧美xxxx| 韩国福利视频一区| 欧美亚洲在线观看| 欧美另类69精品久久久久9999| 日韩中文字幕精品视频| 亚洲精品福利在线观看| 欧美日韩国产麻豆| 午夜精品久久17c| 国产极品jizzhd欧美| 91在线观看免费网站| 亚洲国产精品一区二区久| 国产精品91久久| 另类美女黄大片| 热门国产精品亚洲第一区在线| 欧美极品少妇xxxxⅹ喷水| 影音先锋欧美精品| 深夜福利亚洲导航| 欧亚精品中文字幕| 日韩一区av在线| 亚洲最大成人在线| 色噜噜久久综合伊人一本| 一区二区成人精品| 高清欧美性猛交xxxx| 欧美激情videoshd| 欧美高清视频在线| 一区二区亚洲欧洲国产日韩| 国产视频久久网| 久99久在线视频| 亚洲欧美国产另类| 亚洲人成人99网站| 亚洲精品成人av| 亚洲欧美激情四射在线日| 欧美视频裸体精品| 欧美激情久久久| 国内久久久精品| 日韩精品免费在线观看| 日韩一区二区在线视频| 国产精品网站视频| 久久精品亚洲热| 红桃av永久久久| 久久91亚洲精品中文字幕奶水| 日韩免费黄色av| 亚洲男人天堂2019| 91情侣偷在线精品国产| 国产一区二区三区在线观看视频| 日韩精品久久久久久久玫瑰园| 国产成人精品视频| 欧美高清视频在线观看| 国产xxx69麻豆国语对白| 欧美特黄级在线| 国产三级精品网站| 一区二区三区视频在线| 欧美日韩国产精品一区二区不卡中文| 一区二区三区国产视频| 成人免费福利在线| 久久九九热免费视频| 亚洲人成电影网站色xx| 欧美成人精品影院| 久久久久女教师免费一区| 国产精品视频男人的天堂| 亚洲aa中文字幕| 久久亚洲精品毛片| 欧美日韩综合视频| 久久这里有精品视频| 亚洲一二在线观看| 黄色精品一区二区| 一区二区日韩精品| 亚洲深夜福利视频| 欧美xxxx14xxxxx性爽| 欧洲亚洲女同hd| 亚洲欧美国产精品专区久久| 国产精品美女免费视频| 97国产suv精品一区二区62| 欧美精品videossex性护士| 国产99久久精品一区二区| 亚洲欧美日韩中文在线制服| 国产精品69av| 国产精品久久久久秋霞鲁丝| 国产成人在线亚洲欧美| 97精品一区二区视频在线观看| 日韩视频在线观看免费| 欧美在线一级视频| 亚洲天堂精品在线| 亚洲xxxx做受欧美| 国产一区二区三区精品久久久| 亚洲精品乱码久久久久久按摩观| 亚洲色图色老头| 日韩av免费在线| 亚洲人成电影网站| 日本亚洲精品在线观看| 欧美成人免费全部观看天天性色| 久久久人成影片一区二区三区| 国产欧美精品日韩精品| 欧美激情二区三区| 亚洲第一中文字幕在线观看| 九九久久精品一区| 97国产在线视频| 久久亚洲电影天堂| 亚洲欧美日韩高清| 国产精品男人的天堂| 日韩欧美在线中文字幕| 国产丝袜一区二区三区免费视频| 欧美成人免费一级人片100| 国外成人在线播放| 国内外成人免费激情在线视频网站| 国产精品黄视频| 亚洲成年人在线| 色青青草原桃花久久综合| 在线视频国产日韩| 欧美在线精品免播放器视频| 久久精品91久久香蕉加勒比| 日本亚洲欧美三级| 国产精品久久久久一区二区| 在线一区二区日韩| 国产精品美女主播| 日韩高清有码在线| 久久久女人电视剧免费播放下载| 国产视频丨精品|在线观看| 国产欧美日韩亚洲精品| 亚洲精品v欧美精品v日韩精品| 亚洲乱码国产乱码精品精天堂| 欧美日韩免费在线观看| 深夜精品寂寞黄网站在线观看| 久久精品国产久精国产思思| 欧美中文字幕视频在线观看| 国产丝袜精品第一页| 欧美黄色片视频| 国产精品亚洲美女av网站| 久久手机免费视频| 亚洲成人av中文字幕| 黄色成人av在线| 精品成人在线视频| 精品性高朝久久久久久久| 在线看片第一页欧美| 欧美www视频在线观看| 成人免费淫片视频软件| 91免费看片在线| 亚洲高清在线观看| 亚洲人成网站在线播| 日韩中文字幕第一页| 久久视频在线直播| 中文精品99久久国产香蕉| 久久男人的天堂| 久久久www成人免费精品| 奇米影视亚洲狠狠色| 日韩hd视频在线观看| 欧美激情亚洲激情| 亚洲欧美日韩第一区| 日韩欧美在线第一页|