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

C#多線程之線程同步2,

編輯:關於.NET

C#多線程之線程同步2,


  在上一篇C#多線程之線程同步1中,我們主要學習了執行基本的原子操作、使用Mutex構造以及SemaphoreSlim構造,在這一篇中我們主要學習如何使用AutoResetEvent構造、ManualResetEventSlim構造和CountDownEvent構造。

四、使用AutoResetEvent構造

  在這一小節中,我們將學習如何使用AutoResetEvent構造從一個線程向另一個線程發送通知。AutoResetEvent通知一個等待線程某個事件已經發生。具體步驟如下所示:

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 Recipe04
 7 {
 8     class Program
 9     {
10         private static AutoResetEvent workerEvent = new AutoResetEvent(false);
11         private static AutoResetEvent mainEvent = new AutoResetEvent(false);
12 
13         static void Process(int seconds)
14         {
15             WriteLine("Starting a long running work...");
16             Sleep(TimeSpan.FromSeconds(seconds));
17             WriteLine("Work is done!");
18             workerEvent.Set();
19             WriteLine("Waiting for a main thread to complete its work");
20             mainEvent.WaitOne();
21             WriteLine("Starting second operation...");
22             Sleep(TimeSpan.FromSeconds(seconds));
23             WriteLine("Work is done!");
24             workerEvent.Set();
25         }
26 
27         static void Main(string[] args)
28         {
29             var t = new Thread(() => Process(10));
30             t.Start();
31 
32             WriteLine("Waiting for another thread to complete work");
33             workerEvent.WaitOne();
34             WriteLine("First operation is completed!");
35             WriteLine("Performing an operation on a main thread");
36             Sleep(TimeSpan.FromSeconds(5));
37             mainEvent.Set();
38             WriteLine("Now running the second operation on a second thread");
39             workerEvent.WaitOne();
40             WriteLine("Second operation is completed!");
41         }
42     }
43 }

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

  在第10~11行代碼處,我們定義了兩個AutoResetEvent實例:workerEvent和mainEvent。workerEvent用於從新建線程中向主線程發送通知,mainEvent用於從主線程向新建線程發送通知。在調用AutoResetEvent的構造方法的時候,我們給該構造方法的“initialState”參數傳遞了false值,指定AutoResetEvent實例的初始狀態為“無信號狀態”,這意味著調用AutoResetEvent實例的“WaitOne”方法的線程將會被阻塞,直到我們調用AutoResetEvent實例的“Set”方法之後,該線程才會繼續執行。如果我們將AutoResetEvent類的構造方法的“initialState”參數值設置為true,則AutoResetEvent實例的初始狀態為“信號狀態”,那麼第一個調用AutoResetEvent實例的“WaitOne”方法的線程將會被立即執行,然後AutoResetEvent實例的狀態自動變為“無信號狀態”,這個時候,當我們再次調用AutoResetEvent的“WaitOne”方法後,必須在另一個線程中調用AutoResetEvent的“Set”方法才能繼續執行當前的線程。

  在第29行代碼處,我們創建了一個新的線程用於執行“Process”方法,並在第30行代碼處啟動線程。

  在第33行代碼處,我們調用AutoResetEvent實例workerEvent的“WaitOne”方法,導致主線程被阻塞,然而在我們在第29行代碼處創建的線程中,我們調用了AutoResetEvent實例WorkerEvent的“Set”方法,因此,主線程得以繼續執行。當執行到第20行代碼處,我們在新建線程中調用了AutoResetEvent實例mainEvent的“WaitOne”方法,因此導致新建線程被阻塞,然而在主線程執行到第37行代碼處,我們調用了AutoResetEvent實例mainEvent的“Set”方法,因此,新建線程得以繼續執行。而主線程在執行到第39行代碼處,主線程又被阻塞,而新建線程執行到第24行代碼處,導致主線程得以繼續執行,因此,主線程執行到第40行代碼,控制台應用程序正常結束。

  AutoResetEvent是kernel-time構造,因此,如果沒有必要,我們建議使用下一節介紹的ManualResetEventslim來替代AutoResetEvent。

五、使用ManualResetEventSlim構造

  在這一小節中,我們將學習如何使用ManualResetEventSlim構造在多個線程之間更加靈活地發送通知。具體步驟如下所示:

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         private static ManualResetEventSlim mainEvent = new ManualResetEventSlim(false);
11 
12         static void TravelThroughGates(string threadName, int seconds)
13         {
14             WriteLine($"{threadName} falls to sleep");
15             Sleep(TimeSpan.FromSeconds(seconds));
16             WriteLine($"{threadName} waits for the gates to open!");
17             mainEvent.Wait();
18             WriteLine($"{threadName} enters the gates!");
19         }
20         
21         static void Main(string[] args)
22         {
23             var t1 = new Thread(() => TravelThroughGates("Thread 1", 5));
24             var t2 = new Thread(() => TravelThroughGates("Thread 2", 6));
25             var t3 = new Thread(() => TravelThroughGates("Thread 3", 12));
26 
27             t1.Start();
28             t2.Start();
29             t3.Start();
30 
31             Sleep(TimeSpan.FromSeconds(6));
32             WriteLine("The gates are now open!");
33             mainEvent.Set();
34             Sleep(TimeSpan.FromSeconds(2));
35             mainEvent.Reset();
36             WriteLine("The gates have been closed!");
37             Sleep(TimeSpan.FromSeconds(10));
38             WriteLine("The gates are now open for the second time!");
39             mainEvent.Set();
40             Sleep(TimeSpan.FromSeconds(2));
41             WriteLine("The gates have been closed!");
42             mainEvent.Reset();
43         }
44     }
45 }

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

  在第10行代碼處,我們定義了一個ManualResetEventSlim類型的實例mainEvent,並給它的構造方法的“initialState”參數傳遞了false值,表示該對象的初始狀態為“無信號狀態”。

  在第23~25行代碼處,我們創建了三個線程t1、t2和t3。這三個線程都用於執行“TraveThroughGates”方法,在該方法的內部,我們調用了ManualResetEventSlim實例mainEvent的“Wait”方法,以阻塞t1、t2和t3線程的執行。

  在第31行代碼處,我們讓主線程阻塞6秒鐘,在這六秒鐘內,線程t1和t2都執行到第17行代碼處,這個時候線程t1和t2都阻塞,並且等待mainEvent的“Set”方法被調用,以接收信號後繼續執行。主線程阻塞6秒鐘後,會執行第33行代碼,執行完畢這行代碼之後,線程t1和t2都會接收到通知,因此,線程t1和t2都會繼續往下執行,從而都執行第18行代碼,之後線程t1和t2執行完畢,結束。

  由於線程t3在主線程執行到第33行代碼處的時候,還在阻塞(因為執行了第15行代碼)中,因此線程t3在主線程執行到第33行代碼處的時候不受影響,繼續阻塞。

  當主線程執行到第34行代碼處的時候,線程t3依然在阻塞狀態中。在主線程執行了第35行代碼之後,mainEvent被重置為“無信號狀態”。當主線程執行到第37行代碼處,主線程被阻塞10秒鐘。在主線程被阻塞的10秒鐘內,線程t3會執行到第17行代碼處,從而t3線程被阻塞,等待通知的到來,才能繼續執行。

  當主線程阻塞10秒鐘之後,會執行第39行代碼,從而導致線程t3繼續執行,因此會執行第18行代碼,線程t3結束。

  然後主線程阻塞2秒鐘後,又將mainEvent重置為“無信號狀態”,然後主線程結束。

六、使用CountdownEvent構造

  在這一小節中,我們將學習如何使用CountdownEvent構造等待發送一定數量的通知後,才繼續執行被阻塞的線程。學習步驟如下所示:

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         private static CountdownEvent countdown = new CountdownEvent(2);
11 
12         static void PerformOperation(string message, int seconds)
13         {
14             Sleep(TimeSpan.FromSeconds(seconds));
15             WriteLine(message);
16             countdown.Signal();
17         }
18 
19         static void Main(string[] args)
20         {
21             WriteLine("Starting two operations");
22             var t1 = new Thread(() => PerformOperation("Operation 1 is completed", 4));
23             var t2 = new Thread(() => PerformOperation("Operation 2 is completed", 8));
24 
25             t1.Start();
26             t2.Start();
27             countdown.Wait();
28             WriteLine("Both operations have been completed.");
29             countdown.Dispose();
30         }
31     }
32 }

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

  在第10行代碼處,我們創建了一個CountdownEvent的實例countdown,並給該構造方法的“initialCount”參數傳遞了數值2,表示我們希望等待2個通知發送完畢後,被阻塞的線程才能繼續執行。

  在第22~23行代碼處,我們創建了兩個新線程用於執行“PerformOperation”方法,在該方法中,我們調用了countdown的“Signal”方法,用於發送通知,並減小CountdownEvent的CurrentCount的值,當CurrentCount的值減少到0時,被阻塞的線程才能繼續執行。

  在第27行代碼處,我們在主線程中調用了countdown的“Wait”方法,從而主線程被阻塞,直到接收到通知並且CurrentCount的值為0時,主線程才能繼續執行。

  注意,如果將第10行代碼處的2修改為3,再次運行該程序,主線程會一直等待,不會結束,因為CurrentCount的值沒有減少到0。

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