這篇文章的目的並不是系統地介紹C#中的await
、async
關鍵字,而是針對我遇到的一些問題進行記錄。
背景
await / async
C#中可以用async
標識方法,表示這個方法是異步的。異步方法的返回值必須是void
、Task
或者Task<T>
。例如:
public static async Task<int> Method(int i) { await Task.Delay(1000); return i; }
- 1
- 2
- 3
- 4
- 5
用async
修飾的lambda表達式
我們可以用async
修飾lambda表達式,標識這個表達式為異步。
Func<Task<HttpResponseMessage>> lambda = async () => { using (var httpClient = new HttpClient()) { var response = await httpClient.GetAsync("https://www.bing.com/"); return response; } };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
可以看到,用async
修飾以后,返回值也變為Task<HttpResponseMessage>
。
async
標記只是針對方法內部,外部只關心返回值類型。
異步方法返回的時間
異步方法在遇到await
關鍵字之后就會返回。例如下面的代碼:
private static Stopwatch stopwatch; static void Main(string[] args) { stopwatch = Stopwatch.StartNew(); Task task = MethodAsync(); Console.WriteLine($"Main: {stopwatch.ElapsedMilliseconds}"); } public static async Task MethodAsync() { Console.WriteLine("Enter method."); Task.Delay(500).Wait(); Console.WriteLine($"Method will return: {stopwatch.ElapsedMilliseconds}"); await Task.Delay(1000); Console.WriteLine($"End of method: {stopwatch.ElapsedMilliseconds}"); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
MethodAsync()
方法會在遇到await
時返回。於是就有了如下輸出:
Enter method. Method will return: 542 Main: 544
- 1
- 2
- 3
Task
類的一些方法
Task.Run(Action action)
我們可以用Task.Run(Action action)
來啟動一個任務。
static void Main(string[] args) { stopwatch = Stopwatch.StartNew(); var task = Task.Run(MethodAsync); task.Wait(); Console.WriteLine($"Main: {stopwatch.ElapsedMilliseconds}"); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
其中MethodAsync
就是上面定義的方法。
可以推測,MethodAsync
執行到第一個await
就會返回。Wait()
方法也會執行完畢。整段程序執行500多毫秒就應該結束。
讓我們運行一下。
Enter method. Method will return: 544 End of method: 1546 Main: 1547
- 1
- 2
- 3
- 4
奇怪,MethodAsync
返回不久,整個程序就應該結束才是,為什么等待了一秒多,直到MethodAsync
全部執行完?
Task.Run(Func<Task> function)
原來,Task.Run
那里實際調用的是Task.Run(Func<Task> function)
這個重載。這個重載返回的Task
對象,我們在調用其Wait()
成員方法時會等待function
返回的Task
對象執行結束。我認為應該是為了方便這種情況。
await Task.Run(async () => { using (var httpClient = new HttpClient()) { var response = await httpClient.GetAsync("https://www.bing.com/"); return response; } });
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
在這段代碼中,正是由於調用的是Task.Run(Func<Task> function)
這個重載,才保證了請求完成后才會繼續執行后面的代碼。
System.Threading.Tasks.Parallel
類的情況
System.Threading.Tasks.Parallel
類提供了非常方便的並發執行循環的方法。
Parallel.For(0, 10, i => Console.WriteLine(i));
- 1
輸出
0
2
6
4
5
8
9
7
3
1
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
再試試
Parallel.For(0, 10, async i => { await Task.Delay(1000); Console.WriteLine(i); });
- 1
- 2
- 3
- 4
- 5
輸出
- 1
是的,輸出沒有任何內容。
原因是,Parallel
和Task
不一樣,它並不會等待返回的Task
,而是在方法返回后就結束。
所以,如果想確保異步方法完全完成,必須改為同步。
Parallel.For(0, 10, i => { Task.Delay(1000).Wait(); Console.WriteLine(i); });
- 1
- 2
- 3
- 4
- 5
輸出
8
4
6
2
0
1
7
5
3
9