中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

C#中多線程出現超時如何處理

發布時間:2021-01-21 16:17:34 來源:億速云 閱讀:691 作者:Leah 欄目:編程語言

這篇文章將為大家詳細講解有關C#中多線程出現超時如何處理,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。

處理的是下面這些情況:

  • 我們做了一個應用程序,程序中有這么一個模塊,它的功能向用戶顯示一個消息對話框,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 seconds
Operation started
Starting with timeout of 5 but cancelling after 2 seconds
Finished...

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

解決方案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 seconds
Operation started
Starting with timeout of 5 but cancelling after 2 seconds
Finished...

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

解決方案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 seconds
Operation started
Starting with timeout of 5 but cancelling after 2 seconds
Finished...

我個人非常傾向于這個解決方案,它比我們使用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 times
Operation started
Operation started
Operation started
Starting with timeout of 10 seconds 3 times, but cancelling after 5 seconds
Finished...

很開森對不對?

當我檢查這段代碼時,我發現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 times
Operation started
Operation started
Operation started
Starting with timeout of 10 seconds 3 times, but cancelling after 5 seconds
Operation started
Operation started
Finished...

關于C#中多線程出現超時如何處理就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

广州市| 临澧县| 鄂尔多斯市| 曲阳县| 灵璧县| 醴陵市| 巴彦淖尔市| 永顺县| 铜梁县| 上虞市| 西城区| 西林县| 乌鲁木齐县| 昂仁县| 南和县| 松溪县| 璧山县| 景洪市| 横山县| 巫山县| 聂拉木县| 惠安县| 宁陵县| 松桃| 新乐市| 四川省| 东安县| 甘南县| 康保县| 蒲城县| 资阳市| 福安市| 肃南| 阿拉善右旗| 南安市| 禄劝| 霍州市| 江油市| 杭锦旗| 威远县| 综艺|