[Day5]Task等待處理(Wait、WaitAll、WaitAny、WaitAsync、WhenAll、WhenAny) - C# SyncAndAsync

前言

在上回 Post not found: 使用Task撰寫第一個非同步程式-C-SyncAndAsync [Day4]使用Task撰寫第一個非同步程式 - C# SyncAndAsync ,我們使用了Task來撰寫第一隻非同步的程式。
而在這回將會著重在等待方面。

Task提供以下語法能夠等待:Wait、WaitAll、WaitAny、WaitAsync、WhenAll、WhenAny。

  • Task.Wait:等候 Task 完成執行。
  • Task.WaitAll:等待所有提供的 Task 物件完成執行。
  • Task.WaitAny:等候任一提供的 Task 物件完成執行。
  • Task.WaitAsync:取得 Task 當此 Task 完成或指定的超時時間過期時將完成的。(ps.是.NET6, 7 Preview 1才支援的新語法,目前是用.net core 3.1所以先不做範例)
  • Task.WhenAll:建立當所有提供的工作完成時才會完成的工作。
  • Task.WhenAny:建立當任何一個提供的工作完成時才會完成的工作。

這麼看來,使用Wait、When差別好像很難分清楚?再稍微比較一下:

Task.WhenAll Task.WaitAll
調用時阻塞該線程 不會
返回值 Task
備註 可以用返回的Task去檢查是否完成

此外,Task.WhenAll()也能用前面加await的語法去製造出阻塞。

1
await Task.WhenAll()

因此在使用上,能夠全部使用When再搭配await去組合出Wait的效果。

實作測試

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;

namespace TaskWaitHandle
{
public class Program
{
public static void Main(string[] args)
{
TaskWaitTest();

TaskWaitAllTest();
TaskWhenAllTest();

TaskWaitAnyTest();
TaskWhenAnyTest();
}

/// <summary>
/// 測試Task.WaitAny
/// </summary>
public static void TaskWaitAnyTest()
{
Stopwatch sw = new Stopwatch();
sw.Start();


var tasks = new List<Task> { DelayTime(5000), DelayTime(3000), DelayTime(2000) };
Task.WaitAny(tasks.ToArray());

string second = (sw.Elapsed.TotalMilliseconds / 1000).ToString();
Console.WriteLine(nameof(TaskWaitAnyTest) + " : 一共花費:" + second + "秒");
}

/// <summary>
/// 測試Task.WhenAny
/// </summary>
public static void TaskWhenAnyTest()
{
Stopwatch sw = new Stopwatch();
sw.Start();


var tasks = new List<Task> { DelayTime(5000), DelayTime(3000), DelayTime(2000) };
var t = Task.WhenAny(tasks);

string second = (sw.Elapsed.TotalMilliseconds / 1000).ToString();
Console.WriteLine(nameof(TaskWhenAnyTest) + " : 一共花費:" + second + "秒");
}

/// <summary>
/// 測試Task.WhenAll
/// </summary>
public static void TaskWhenAllTest()
{
Stopwatch sw = new Stopwatch();
sw.Start();


var tasks = new List<Task> { DelayTime(5000), DelayTime(3000), DelayTime(2000) };
var t = Task.WhenAll(tasks);

string second = (sw.Elapsed.TotalMilliseconds / 1000).ToString();
Console.WriteLine(nameof(TaskWhenAllTest) + " : 一共花費:" + second + "秒");
}

/// <summary>
/// 測試Task.WaitAll
/// </summary>
public static void TaskWaitAllTest()
{
Stopwatch sw = new Stopwatch();
sw.Start();


var tasks = new List<Task> { DelayTime(5000), DelayTime(3000), DelayTime(2000) };
Task.WaitAll(tasks.ToArray());

sw.Stop();
string second = (sw.Elapsed.TotalMilliseconds / 1000).ToString();
Console.WriteLine(nameof(TaskWaitAllTest) + " : 一共花費:" + second + "秒");
}

/// <summary>
/// 測試Task.Wait
/// </summary>
public static void TaskWaitTest()
{
Stopwatch sw = new Stopwatch();
sw.Start();


Task t = DelayTime(5000);
t.Wait();
sw.Stop();


string second = (sw.Elapsed.TotalMilliseconds / 1000).ToString();
Console.WriteLine(nameof(TaskWaitTest) + " : 一共花費:" + second + "秒");
}

/// <summary>
/// 停止一段時間
/// </summary>
/// <param name="millseconds">停止毫秒</param>
/// <returns></returns>
public static async Task DelayTime(int millseconds)
{
await Task.Delay(millseconds);
}
}
}

執行完結果:

1
2
3
4
5
TaskWaitTest : 一共花費:5.0991762秒
TaskWaitAllTest : 一共花費:5.017742599999999秒
TaskWhenAllTest : 一共花費:0.0008574秒
TaskWaitAnyTest : 一共花費:2.0228913秒
TaskWhenAnyTest : 一共花費:0.0006043秒

可以見到When不會阻塞所以直接被帶過,而Wait造成阻塞。

總結

可以知道Task使用Wait會阻塞,When不會阻塞。
而使用When可以再之後去檢查該返回值是否完成。

至於如果想讓When阻塞,就使用await即可。
所以總結是:When搭await可以組出所有阻塞/非阻塞,因此一路組合技用到底即可,而Wait可以擺一邊放置了。

參考資料