C#多線程之線程池篇3

来源:http://www.cnblogs.com/yonghuacui/archive/2016/12/28/6229183.html
-Advertisement-
Play Games

在上一篇C#多線程之線程池篇2中,我們主要學習了線程池和並行度以及如何實現取消選項的相關知識。在這一篇中,我們主要學習如何使用等待句柄和超時、使用計時器和使用BackgroundWorker組件的相關知識。 五、使用等待句柄和超時 在這一小節中,我們將學習如何線上程池中實現超時和正確地實現等待。具體 ...


  在上一篇C#多線程之線程池篇2中,我們主要學習了線程池和並行度以及如何實現取消選項的相關知識。在這一篇中,我們主要學習如何使用等待句柄和超時、使用計時器和使用BackgroundWorker組件的相關知識。

五、使用等待句柄和超時

  在這一小節中,我們將學習如何線上程池中實現超時和正確地實現等待。具體操作步驟如下:

1、使用Visual Studio 2015創建一個新的控制台應用程式。

2、雙擊打開“Program.cs”文件,編寫代碼如下所示:

 1 using System;
 2 using System.Threading;
 3 using static System.Console;
 4 using static System.Threading.Thread;
 5 
 6 namespace Recipe05
 7 {
 8     class Program
 9     {
10         // CancellationTokenSource:通知System.Threading.CancellationToken,告知其應被取消。
11         static void WorkerOperationWait(CancellationTokenSource cts, bool isTimedOut)
12         {
13             if (isTimedOut)
14             {
15                 // 傳達取消請求。
16                 cts.Cancel();
17                 WriteLine("Worker operation timed out and was canceled.");
18             }
19             else
20             {
21                 WriteLine("Worker operation succeeded.");
22             }
23         }
24 
25         // CancellationToken:傳播有關應取消操作的通知。
26         // ManualResetEvent:通知一個或多個正在等待的線程已發生事件。
27         static void WorkerOperation(CancellationToken token, ManualResetEvent evt)
28         {
29             for (int i = 0; i < 6; i++)
30             {
31                 // 獲取是否已請求取消此標記。如果已請求取消此標記,則為 true;否則為 false。
32                 if (token.IsCancellationRequested)
33                 {
34                     return;
35                 }
36                 Sleep(TimeSpan.FromSeconds(1));
37             }
38             // 將事件狀態設置為終止狀態,允許一個或多個等待線程繼續。
39             evt.Set();
40         }
41 
42         static void RunOperations(TimeSpan workerOperationTimeout)
43         {
44             using (var evt = new ManualResetEvent(false))
45             using (var cts = new CancellationTokenSource())
46             {
47                 // 註冊一個等待System.Threading.WaitHandle的委托,並指定一個System.TimeSpan值來表示超時時間。
48                 // 第一個參數:要註冊的System.Threading.WaitHandle。使用System.Threading.WaitHandle而非 System.Threading.Mutex。
49                 // 第二個參數:waitObject參數終止時調用的System.Threading.WaitOrTimerCallback 委托。
50                 // 第三個參數:傳遞給委托的對象。
51                 // 第四個參數:System.TimeSpan表示的超時時間。如果timeout為0(零),則函數將測試對象的狀態並立即返回。如果timeout為 -1,則函數的超時間隔永遠不過期。
52                 // 第五個參數:如果為true,表示在調用了委托後,線程將不再在waitObject參數上等待;如果為false,表示每次完成等待操作後都重置計時器,直到註銷等待。
53                 // 返回值:封裝本機句柄的System.Threading.RegisteredWaitHandle。
54                 var worker = ThreadPool.RegisterWaitForSingleObject(evt, (state, isTimedOut) => WorkerOperationWait(cts, isTimedOut), null, workerOperationTimeout, true);
55 
56                 WriteLine("Starting long running operation...");
57                 // ThreadPool.QueueUserWorkItem:將方法排入隊列以便執行。此方法在有線程池線程變得可用時執行。
58                 // cts.Token:獲取與此System.Threading.CancellationTokenSource關聯的System.Threading.CancellationToken。
59                 ThreadPool.QueueUserWorkItem(_ => WorkerOperation(cts.Token, evt));
60 
61                 Sleep(workerOperationTimeout.Add(TimeSpan.FromSeconds(2)));
62 
63                 // 取消由System.Threading.ThreadPool.RegisterWaitForSingleObject方法發出的已註冊等待操作。
64                 worker.Unregister(evt);
65             }
66         }
67 
68         static void Main(string[] args)
69         {
70             // 實現超時
71             RunOperations(TimeSpan.FromSeconds(5));
72             // 實現等待
73             RunOperations(TimeSpan.FromSeconds(7));
74         }
75     }
76 }

3、運行該控制台應用程式,運行效果如下圖所示:

  線程池還有另一個有用的方法:ThreadPool.RegisterWaitForSingleObject,該方法允許我們將回調方法放入線程池的隊列中,當所提供的等待句柄發送信號或者超時發生時,該回調方法即被執行。這允許我們對線程池中的操作實現超時。

  在第71行代碼處,我們在主線程中調用了“RunOperations”方法,並給它的workerOperationTimeout參數傳遞了數值5,表示超時時間為5秒。

  在第54行代碼處,我們調用了ThreadPool的“RegisterWaitForSingleObject”靜態方法,並指定了回調方法所要執行的操作是“WorkerOperationWait”方法,超時時間是5秒。

  在第59行代碼處,我們調用ThreadPool的“QueueUserWorkItem”靜態方法來執行“WorkerOperation”方法,而該方法所消耗的時間為6秒,在這六秒中內已經線上程池中發送了超時,所以會執行第13~18行和第32~35行處的代碼。

  在第73行代碼處,我們傳遞了數值7給“RunOperations”方法,設置線程池的超時時間為7秒,因為“WorkerOperation”方法的執行時間為6秒,所以在這種情況下沒有發生超時,成功執行完畢“WorkerOperation”方法。

 六、使用計時器

  在這一小節中,我們將學習如何使用System.Threading.Timer對象線上程池中定期地調用一個非同步操作。具體操作步驟如下所示:

1、使用Visual Studio 2015創建一個新的控制台應用程式。

2、雙擊打開“Program.cs”文件,編寫代碼如下所示:

 1 using System;
 2 using System.Threading;
 3 using static System.Console;
 4 using static System.Threading.Thread;
 5 
 6 namespace Recipe06
 7 {
 8     class Program
 9     {
10         static Timer timer;
11 
12         static void TimerOperation(DateTime start)
13         {
14             TimeSpan elapsed = DateTime.Now - start;
15             WriteLine($"{elapsed.Seconds} seconds from {start}. Timer thread pool thread id: {CurrentThread.ManagedThreadId}");
16         }
17 
18         static void Main(string[] args)
19         {
20             WriteLine("Press 'Enter' to stop the timer...");
21             DateTime start = DateTime.Now;
22             // 初始化Timer類的新實例,使用System.TimeSpan值來度量時間間隔。
23             // 第一個參數:一個System.Threading.TimerCallback委托,表示要執行的方法。
24             // 第二個參數:一個包含回調方法要使用的信息的對象,或者為null。
25             // 第三個參數:System.TimeSpan,表示在callback參數調用它的方法之前延遲的時間量。指定-1毫秒以防止啟動計時器。指定零(0)可立即啟動計時器。
26             // 第四個參數:在調用callback所引用的方法之間的時間間隔。指定-1毫秒可以禁用定期終止。
27             timer = new Timer(_ => TimerOperation(start), null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2));
28             try
29             {
30                 Sleep(TimeSpan.FromSeconds(6));
31                 // 更改計時器的啟動時間和方法調用之間的時間間隔,使用System.TimeSpan值度量時間間隔。
32                 // 第一個參數:一個System.TimeSpan,表示在調用構造System.Threading.Timer時指定的回調方法之前的延遲時間量。指定負-1毫秒以防止計時器重新啟動。指定零(0)可立即重新啟動計時器。
33                 // 第二個參數:在構造System.Threading.Timer時指定的回調方法調用之間的時間間隔。指定-1毫秒可以禁用定期終止。
34                 timer.Change(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(4));
35                 ReadLine();
36             }
37             finally
38             {
39                 timer.Dispose();
40             }
41         }
42     }
43 }

3、運行該控制台應用程式,運行效果(每次運行效果可能不同)如下圖所示:

  首先,我們創建了一個Timer實例,它的構造方法的第一個參數是一個lambda表達式,表示要線上程池中執行的代碼,在該表達式中我們調用了“TimerOperation”方法,並給它提供了一個開始時間值。由於我們沒有使用state對象,因此我們給Timer的構造方法的第二個參數傳遞了null。第三個參數表示第一次執行“TimerOperation”所要花費的時間為1秒鐘。第四個參數表示每次調用“TimerOperation”之間的時間間隔為2秒鐘。

  在主線程阻塞6秒鐘之後,我們調用了Timer實例的“Change”方法,更改了每次調用“TimerOperation”之間的時間間隔為4秒鐘。

  最後,我們等待輸入“Enter”鍵來結束應用程式。

七、使用BackgroundWorker組件

   在這一小節中,我們學習另外一種非同步編程的方式:BackgroundWorker組件。在這個組件的幫助下,我們可以通過一系列事件和事件處理方法組織我們的非同步代碼。具體操作步驟如下所示:

1、使用Visual Studio 2015創建一個新的控制台應用程式。

2、雙擊打開“Program.cs”文件,編寫代碼如下所示:

  1 using System;
  2 using System.ComponentModel;
  3 using System.Threading;
  4 using static System.Console;
  5 using static System.Threading.Thread;
  6 
  7 namespace Recipe07
  8 {
  9     class Program
 10     {
 11         static void WorkerDoWork(object sender, DoWorkEventArgs e)
 12         {
 13             WriteLine($"DoWork thread pool thread id: {CurrentThread.ManagedThreadId}");
 14             var bw = (BackgroundWorker)sender;
 15             for (int i = 1; i <= 100; i++)
 16             {
 17                 // 獲取一個值,指示應用程式是否已請求取消後臺操作。
 18                 // 如果應用程式已請求取消後臺操作,則為 true;否則為 false。預設值為 false。
 19                 if (bw.CancellationPending)
 20                 {
 21                     e.Cancel = true;
 22                     return;
 23                 }
 24 
 25                 if (i % 10 == 0)
 26                 {
 27                     // 引發 System.ComponentModel.BackgroundWorker.ProgressChanged 事件。
 28                     // 參數:已完成的後臺操作所占的百分比,範圍從 0% 到 100%。
 29                     bw.ReportProgress(i);
 30                 }
 31 
 32                 Sleep(TimeSpan.FromSeconds(0.1));
 33             }
 34 
 35             // 獲取或設置表示非同步操作結果的值。
 36             e.Result = 42;
 37         }
 38 
 39         static void WorkerProgressChanged(object sender, ProgressChangedEventArgs e)
 40         {
 41             // e.ProgressPercentage:獲取非同步任務的進度百分比。
 42             WriteLine($"{e.ProgressPercentage}% completed. Progress thread pool thread id: {CurrentThread.ManagedThreadId}");
 43         }
 44 
 45         static void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
 46         {
 47             WriteLine($"Completed thread pool thread id: {CurrentThread.ManagedThreadId}");
 48             // e.Error:獲取一個值,該值指示非同步操作期間發生的錯誤。
 49             if (e.Error != null)
 50             {
 51                 // 列印出非同步操作期間發生的錯誤信息。
 52                 WriteLine($"Exception {e.Error.Message} has occured.");
 53             }
 54             else if (e.Cancelled) // 獲取一個值,該值指示非同步操作是否已被取消。
 55             {
 56                 WriteLine($"Operation has been canceled.");
 57             }
 58             else
 59             {
 60                 // e.Result:獲取表示非同步操作結果的值。
 61                 WriteLine($"The answer is: {e.Result}");
 62             }
 63         }
 64 
 65         static void Main(string[] args)
 66         {
 67             // 初始化System.ComponentModel.BackgroundWorker類的新實例。該類在單獨的線程上執行操作。
 68             var bw = new BackgroundWorker();
 69             // 獲取或設置一個值,該值指示System.ComponentModel.BackgroundWorker能否報告進度更新。
 70             // 如果System.ComponentModel.BackgroundWorker支持進度更新,則為true;否則為false。預設值為false。
 71             bw.WorkerReportsProgress = true;
 72             // 獲取或設置一個值,該值指示System.ComponentModel.BackgroundWorker是否支持非同步取消。
 73             // 如果System.ComponentModel.BackgroundWorker支持取消,則為true;否則為false。預設值為false。
 74             bw.WorkerSupportsCancellation = true;
 75 
 76             // 調用System.ComponentModel.BackgroundWorker.RunWorkerAsync時發生。
 77             bw.DoWork += WorkerDoWork;
 78             // 調用System.ComponentModel.BackgroundWorker.ReportProgress(System.Int32)時發生。
 79             bw.ProgressChanged += WorkerProgressChanged;
 80             // 當後臺操作已完成、被取消或引發異常時發生。
 81             bw.RunWorkerCompleted += WorkerCompleted;
 82 
 83             // 開始執行後臺操作。
 84             bw.RunWorkerAsync();
 85 
 86             WriteLine("Press C to cancel work");
 87 
 88             do
 89             {
 90                 // 獲取用戶按下的下一個字元或功能鍵。按下的鍵可以選擇顯示在控制台視窗中。
 91                 // 確定是否在控制台視窗中顯示按下的鍵。如果為 true,則不顯示按下的鍵;否則為 false。
 92                 if (ReadKey(true).KeyChar == 'C')
 93                 {
 94                     // 請求取消掛起的後臺操作。
 95                     bw.CancelAsync();
 96                 }
 97             }
 98             // 獲取一個值,指示System.ComponentModel.BackgroundWorker是否正在運行非同步操作。
 99             // 如果System.ComponentModel.BackgroundWorker正在運行非同步操作,則為true;否則為false。
100             while (bw.IsBusy);
101         }
102     }
103 }

3、運行該控制台應用程式,運行效果(每次運行效果可能不同)如下圖所示:

   在第68行代碼處,我們創建了一個BackgroundWorker組件的實例,並且在第71行代碼和第74行代碼處明確地說明該實例支持進度更新和非同步取消操作。

  在第77行代碼、第79行代碼和第81行代碼處,我們給該實例掛載了三個事件處理方法。每當DoWork、ProgressChanged和RunWorkerCompleted事件發生時,都會執行相應的“WorkerDoWork方法”、“WorkerProgressChanged”方法和“WorkerCompleted”方法。

  其他代碼請參考註釋。

  源碼下載


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本人在官網下的是這個 CentOS-7-x86_64-DVD-1611.iso ,然後用UltraISO 9.6製作的U盤啟動盤,不過在安裝的時候出現了這個錯誤, 然後也是搜了好久,試了一下,下麵這個方法,是正確可行的.在最後我會解釋原因 第一步: 製作完成之後,將U盤的標簽名字改掉,隨便改成什麼, ...
  • linux許可權位 Linux文件或目錄的許可權位是由9個許可權位來控制,每三位為一組,它們分別是文件屬主許可權、屬組許可權、其他用戶許可權。 r:read可讀許可權,對應數字4; w:write可寫許可權,對應數字2; x:execute執行許可權,對應數字1; -:沒有任何許可權,對應數字0。 #創建一個用戶組 [... ...
  • 前面講到,使用非阻塞方式有許多的缺點。主要是單線程,一直占用CPU資源,其他程式無法執行,造成資源的浪費。只能用於較簡單迴圈的場所。而線程靈活,CPU占用率小,適用於大部分場合。 1.條件變數的使用 條件變數是用來通知共用數據的狀態信息的機制。由於涉及共用數據,所以一般和互斥量配合使用。 1.1創建 ...
  • 一、 前期準備 Ps:工行運營提供開發文檔時,沒有直接提供支付組件ICBCEBankUtil.dll和infosecapi.dll,工行運營說有這個組件在文檔中,但是沒有找到,工行沒有技術提供支持,後來在網上自己找的。 二、 環境搭配 Ps:本人使用的是64位系統,支付組件是放在System64文件 ...
  • 本文版權歸博客園和作者吳雙本人共同所有,轉載和爬蟲必須在顯要位置註明原文地址,www.cnblogs.com/tdws 寫在前面 圖片/文件伺服器,顧名思義就是存文件唄,有的人用阿裡雲的現有服務,有的把文件Post到文件伺服器,在文件伺服器一端用一個應用程式來接收並保存,方法各不相同。老司機們各種服 ...
  • 本人大腦已短路...........肯定可以簡化 但是本人暈乎乎的決定關機睡覺去............ ...
  • 在IE8瀏覽器以後版本,都有一個“相容性視圖”,讓不少新技術無法使用。那麼如何禁止瀏覽器自動選擇“相容性視圖”,強制IE以最高級別的可用模式顯示內容呢? ...
  • ajax請求: $(function () { $.ajax({ url: "index.aspx?method=send", success: function (data) { JSON.parse(data).forEach(function (item) { console.log(item ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...