程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#多線程之線程池篇3,

C#多線程之線程池篇3,

編輯:C#入門知識

C#多線程之線程池篇3,


  在上一篇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”方法。

  其他代碼請參考注釋。

  源碼下載

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved