真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

C#中多線程出現(xiàn)超時(shí)如何處理

這篇文章將為大家詳細(xì)講解有關(guān)C#中多線程出現(xiàn)超時(shí)如何處理,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。

成都創(chuàng)新互聯(lián)主營盤錦網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,重慶APP軟件開發(fā),盤錦h5微信小程序搭建,盤錦網(wǎng)站營銷推廣歡迎盤錦等地區(qū)企業(yè)咨詢

處理的是下面這些情況:

  • 我們做了一個(gè)應(yīng)用程序,程序中有這么一個(gè)模塊,它的功能向用戶顯示一個(gè)消息對(duì)話框,15秒后再自動(dòng)關(guān)閉該對(duì)話框。但是,如果用戶手動(dòng)關(guān)閉對(duì)話框,則在timeout時(shí)我們無需做任何處理。

  • 程序中有一個(gè)漫長的執(zhí)行操作。如果該操作持續(xù)5秒鐘以上,那么請(qǐng)終止這個(gè)操作。

  • 我們的的應(yīng)用程序中有執(zhí)行時(shí)間未知的操作。當(dāng)執(zhí)行時(shí)間過長時(shí),我們需要顯示一個(gè)“進(jìn)行中”彈出窗口來提示用戶耐心等待。我們無法預(yù)估這次操作會(huì)持續(xù)多久,但一般情況下會(huì)持續(xù)不到一秒。為了避免彈出窗口一閃而過,我們只想要在1秒后顯示這個(gè)彈出窗口。反之,如果在1秒內(nèi)操作完成,則不需要顯示這個(gè)彈出窗口。

這些問題是相似的。在超時(shí)之后,我們必須執(zhí)行X操作,除非Y在那個(gè)時(shí)候發(fā)生。

為了找到解決這些問題的辦法,我在試驗(yàn)過程中創(chuàng)建了一個(gè)類:

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

我的操作類:

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

我的測(cè)試程序:

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();
}

結(jié)果應(yīng)該是:

Starting with timeout of 5 seconds
Operation started
Starting with timeout of 5 but cancelling after 2 seconds
Finished...

現(xiàn)在我們可以開始試驗(yàn)了!

解決方案1:在另一個(gè)線程上休眠

我最初的計(jì)劃是在另一個(gè)不同的線程上休眠,同時(shí)用一個(gè)布爾值來標(biāo)記Stop是否被調(diào)用。

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;
 }
}

針對(duì)正常的線程執(zhí)行步驟,這段代碼運(yùn)行過程并沒有出現(xiàn)問題,但是總是感覺有些別扭。仔細(xì)探究后,我發(fā)現(xiàn)其中有一些貓膩。首先,在超時(shí)期間,有一個(gè)線程從線程池中取出后什么都沒做,顯然這個(gè)線程是被浪費(fèi)了。其次,如果程序停止執(zhí)行了,線程會(huì)繼續(xù)休眠直到超時(shí)結(jié)束,浪費(fèi)了CPU時(shí)間。

但是這些并不是我們這段代碼最糟糕的事情,實(shí)際上我們的程序?qū)嵾€存在一個(gè)明顯的bug:

如果我們?cè)O(shè)置10秒的超時(shí)時(shí)間,開始操作后,2秒停止,然后在2秒內(nèi)再次開始。

當(dāng)?shù)诙螁?dòng)時(shí),我們的_stopCalled標(biāo)志將變成false。然后,當(dāng)我們的第一個(gè)Thread.Sleep()完成時(shí),即使我們?nèi)∠矔?huì)調(diào)用DoOperation。

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

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

當(dāng)StopOperationIfNotStartedYet被調(diào)用時(shí),我們需要某種方式來取消DoOperation的調(diào)用。

如果我們嘗試使用計(jì)時(shí)器呢?

解決方案2:使用計(jì)時(shí)器

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

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

  • System.Timers命名空間下的Timer類。

  • System.Threading.Timer類。

這三種計(jì)時(shí)器中,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();
 }
}

執(zhí)行結(jié)果如下:

Starting with timeout of 5 seconds
Operation started
Starting with timeout of 5 but cancelling after 2 seconds
Finished...

現(xiàn)在當(dāng)我們停止操作時(shí),定時(shí)器被丟棄,這樣就避免了再次執(zhí)行操作。這已經(jīng)實(shí)現(xiàn)了我們最初的想法,當(dāng)然還有另一種方式來處理這個(gè)問題。

解決方案3:ManualResetEvent或AutoResetEvent

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

ManualResetEvent類和AutoResetEvent類請(qǐng)參閱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

言歸正傳,在本例中,直到手動(dòng)重置事件信號(hào)出現(xiàn),mre.WaitOne()會(huì)一直等待。 mre.Set()將標(biāo)記重置事件信號(hào)。 ManualResetEvent將釋放當(dāng)前正在等待的所有線程。AutoResetEvent將只釋放一個(gè)等待的線程,并立即變?yōu)闊o信號(hào)。WaitOne()也可以接受超時(shí)作為參數(shù)。 如果Set()在超時(shí)期間未被調(diào)用,則線程被釋放并且WaitOne()返回False。

以下是此功能的實(shí)現(xiàn)代碼:

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();
 }
}

執(zhí)行結(jié)果:

Starting with timeout of 5 seconds
Operation started
Starting with timeout of 5 but cancelling after 2 seconds
Finished...

我個(gè)人非常傾向于這個(gè)解決方案,它比我們使用Timer的解決方案更干凈簡潔。
對(duì)于我們提出的簡單功能,ManualResetEvent和Timer解決方案都可以正常工作。 現(xiàn)在讓我們?cè)黾狱c(diǎn)挑戰(zhàn)性。

新的改進(jìn)需求

假設(shè)我們現(xiàn)在可以連續(xù)多次調(diào)用StartWithTimeout(),而不是等待第一個(gè)超時(shí)完成后調(diào)用。

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

  1. 在以前的StartWithTimeout超時(shí)期間調(diào)用StartWithTimeout時(shí):忽略第二次啟動(dòng)。

  2. 在以前的StartWithTimeout超時(shí)期間調(diào)用StartWithTimeout時(shí):停止初始話Start并使用新的StartWithTimeout。

  3. 在以前的StartWithTimeout超時(shí)期間調(diào)用StartWithTimeout時(shí):在兩個(gè)啟動(dòng)中調(diào)用DoOperation。 在StopOperationIfNotStartedYet中停止所有尚未開始的操作(在超時(shí)時(shí)間內(nèi))。

  4. 在以前的StartWithTimeout超時(shí)期間調(diào)用StartWithTimeout時(shí):在兩個(gè)啟動(dòng)中調(diào)用DoOperation。 在StopOperationIfNotStartedYet停止一個(gè)尚未開始的隨機(jī)操作。

可能性1可以通過Timer和ManualResetEvent可以輕松實(shí)現(xiàn)。 事實(shí)上,我們已經(jīng)在我們的Timer解決方案中涉及到了這個(gè)。

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

可能性2也可以很容易地實(shí)現(xiàn)。 這個(gè)地方請(qǐng)?jiān)试S我賣個(gè)萌,代碼自己寫哈^_^

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

可能性4跟可能性3相似,可以通過定時(shí)器的集合來實(shí)現(xiàn)。

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

讓我們了解一下這里面遇到的難點(diǎn):

假設(shè)我們調(diào)用StartWithTimeout 10秒超時(shí)。

1秒后,我們?cè)俅握{(diào)用另一個(gè)StartWithTimeout,超時(shí)時(shí)間為10秒。

再過1秒后,我們?cè)俅握{(diào)用另一個(gè)StartWithTimeout,超時(shí)時(shí)間為10秒。

預(yù)期的行為是這3個(gè)操作會(huì)依次10秒、11秒和12秒后啟動(dòng)。

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

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

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();
 });
 }
}

輸出結(jié)果跟預(yù)想的一樣:

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...

很開森對(duì)不對(duì)?

當(dāng)我檢查這段代碼時(shí),我發(fā)現(xiàn)Thread.Sleep(10)是必不可少的,這顯然超出了我的意料。 如果沒有它,除3個(gè)等待中的線程之外,只有1-2個(gè)線程正在進(jìn)行。 很明顯的是,因?yàn)镽eset()發(fā)生得太快,第三個(gè)線程將停留在WaitOne()上。

可能性4:單個(gè)AutoResetEvent停止一個(gè)隨機(jī)操作

假設(shè)我們調(diào)用StartWithTimeout 10秒超時(shí)。1秒后,我們?cè)俅握{(diào)用另一個(gè)StartWithTimeout,超時(shí)時(shí)間為10秒。再過1秒后,我們?cè)俅握{(diào)用另一個(gè)StartWithTimeout,超時(shí)時(shí)間為10秒。然后我們調(diào)用StopOperationIfNotStartedYet()。

目前有3個(gè)操作超時(shí),等待啟動(dòng)。 預(yù)期的行為是其中一個(gè)被停止, 其他2個(gè)操作應(yīng)該能夠正常啟動(dòng)。

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

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();
 }
}

執(zhí)行結(jié)果是:

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...

關(guān)于C#中多線程出現(xiàn)超時(shí)如何處理就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。


當(dāng)前名稱:C#中多線程出現(xiàn)超時(shí)如何處理
網(wǎng)頁鏈接:http://weahome.cn/article/igigce.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部