C#中對異步方法及異步lambda表達式


這篇文章的目的並不是系統地介紹C#中的awaitasync關鍵字,而是針對我遇到的一些問題進行記錄。

背景

await / async

C#中可以用async標識方法,表示這個方法是異步的。異步方法的返回值必須是voidTask或者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

是的,輸出沒有任何內容。

原因是,ParallelTask不一樣,它並不會等待返回的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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM