解決一個問題
假如,程序需要向一個 Web 發送 5 次請求,受網路波動影響,有一定幾率請求失敗。如果失敗了,就需要重試。
示例代碼如下:
class Program
{
private static int count = 0;
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
new Thread(HttpRequest).Start(); // 創建線程
// 用於不斷向另一個線程發送信號
while (count < 5)
{
Thread.Sleep(100);
}
Console.WriteLine("任務執行完畢");
}
// 模擬網絡請求
public static void HttpRequest()
{
Console.WriteLine("開始一個任務");
// 隨機生成一個數,如果為偶數,則模擬請求失敗
bool isSuccess = (new Random().Next(0, 10)) % 2 == 0;
// ... ...模擬請求 HTTP
Thread.Sleep(TimeSpan.FromSeconds(2));
// 請求失敗則重試
if (!isSuccess)
{
Console.WriteLine($"請求失敗,count={count}");
new Thread(() =>
{
HttpRequest();
}).Start();
return;
}
// 完成一次任務,+1
Interlocked.Add(ref count,1);
Console.WriteLine($"完成任務,count={count}");
}
}
代碼太糟糕了,但我們可以使用 CountdownEvent 類來改造它。
CountdownEvent 類
表示在計數變為零時處於有信號狀態的同步基元。
也就是說,設定一個計數器,每個線程完成后,就會減去 1 ,當計數器為 0 時,代表所有線程都已經完成了任務。
構造函數和方法
CountdownEvent 類的構造函數如下:
構造函數 | 說明 |
---|---|
CountdownEvent(Int32) | 使用指定計數初始化 CountdownEvent 類的新實例。 |
CountdownEvent 類的常用方法如下:
方法 | 說明 |
---|---|
AddCount() | 將 CountdownEvent 的當前計數加 1。 |
AddCount(Int32) | 將 CountdownEvent 的當前計數增加指定值。 |
Reset() | 將 CurrentCount 重置為 InitialCount 的值。 |
Reset(Int32) | 將 InitialCount 屬性重新設置為指定值。 |
Signal() | 向 CountdownEvent 注冊信號,同時減小 CurrentCount 的值。 |
Signal(Int32) | 向 CountdownEvent 注冊多個信號,同時將 CurrentCount 的值減少指定數量。 |
TryAddCount() | 增加一個 CurrentCount 的嘗試。 |
TryAddCount(Int32) | 增加指定值的 CurrentCount 的嘗試。 |
Wait() | 阻止當前線程,直到設置了 CountdownEvent 為止。 |
Wait(CancellationToken) | 阻止當前線程,直到設置了 CountdownEvent 為止,同時觀察 CancellationToken。 |
Wait(Int32) | 阻止當前線程,直到設置了 CountdownEvent 為止,同時使用 32 位帶符號整數測量超時。 |
Wait(Int32, CancellationToken) | 阻止當前線程,直到設置了 CountdownEvent 為止,並使用 32 位帶符號整數測量超時,同時觀察 CancellationToken。 |
Wait(TimeSpan) | 阻止當前線程,直到設置了 CountdownEvent 為止,同時使用 TimeSpan 測量超時。 |
Wait(TimeSpan, CancellationToken) | 阻止當前線程,直到設置了 CountdownEvent 為止,並使用 TimeSpan 測量超時,同時觀察 CancellationToken。 |
API 比較多,沒事,我們來慢慢了解它。
示例
我們來編寫一個場景代碼,一個有五件事,需要完成,分別派出 5 個人去實現。
.Wait();
用在一個線程中,這個線程將等待其它完成都完成任務后,才能繼續往下執行。
Signal();
用於工作線程中,向 CountdownEvent 對象發送信號,告知線程已經完成任務,然后 CountdownEvent.CurrentCount
將減去 1。
當計數器為 0 時,阻塞的線程將恢復執行。
代碼示例如下:
class Program
{
// 手頭上有 5 件事
private static CountdownEvent countd = new CountdownEvent(5);
static void Main(string[] args)
{
Console.WriteLine("開始交待任務");
// 同時叫 5 個人,去做 5 件事
for (int i = 0; i < 5; i++)
{
Thread thread = new Thread(DoOne);
thread.Name = $"{i}";
thread.Start();
}
// 等他們都完成事情
countd.Wait();
Console.WriteLine("任務完成,線程退出");
Console.ReadKey();
}
public static void DoOne()
{
int n = new Random().Next(0, 10);
// 模擬要 n 秒才能完成
Thread.Sleep(TimeSpan.FromSeconds(n));
// 完成了,減去一件事
countd.Signal();
Console.WriteLine($" {Thread.CurrentThread.Name}完成一件事了");
}
}
示例很簡單,每個線程在完成自己的任務時,需要調用 Signal()
方法,使得計數器減去1。
.Wait();
可以等待所有的任務完成。
需要注意的是,如果不調用 Signal()
或者計數器一直不為0,那么 Wait()
將無限等待。
當然,Wait()
可以設置等待時間,
另外我們也看到了常用方法中有 AddCount()
、Reset()
等。
這個類的等待控制方式比較寬松,Wait()
后,到底什么時候才能執行,全憑其它線程自覺。
如果發現線程執行任務失敗,我們可以不調用 Signal()
或者 使用 AddCount()
來增加次數,進行重試