C# 異步編程基礎 async/await

  這是學習異步編程的入門篇。html

  涉及 C# 5.0 引入的 async/await,但在控制檯輸出示例時常常會採用 C# 6.0 的 $"" 來拼接字符串,至關於string.Format() 方法。數據庫

 

目錄

 

1、What's 異步?

     啓動程序時,系統會在內存中建立一個新的進程。進程是構成運行程序資源的集合。編程

     在進程內部,有稱爲線程的內核對象,它表明的是真正的執行程序。系統會在 Main 方法的第一行語句就開始線程的執行。服務器

 

     線程網絡

     ①默認狀況,一個進程只包含一個線程,從程序的開始到執行結束;dom

     ②線程能夠派生自其它線程,因此一個進程能夠包含不一樣狀態的多個線程,來執行程序的不一樣部分;異步

     ③一個進程中的多個線程,將共享該進程的資源;async

     ④系統爲處理器執行所規劃的單元是線程,而非進程異步編程

 

     通常來講咱們寫的控制檯程序都只使用了一個線程,從第一條語句按順序執行到最後一條。但在不少的狀況下,這種簡單的模型會在性能或用戶體驗上很差。性能

     例如:服務器要同時處理來自多個客戶端程序的請求,又要等待數據庫和其它設備的響應,這將嚴重影響性能。程序不該該將時間浪費在響應上,而要在等待的同時執行其它任務!

     如今咱們開始進入異步編程。在異步程序中,代碼不須要按照編寫時的順序執行。這時咱們須要用到 C# 5.0 引入的 async/await 來構建異步方法。

 

     咱們先看一下不用異步的示例:

複製代碼

 1     class Program
 2     {
 3         //建立計時器
 4         private static readonly Stopwatch Watch = new Stopwatch();
 5 
 6         private static void Main(string[] args)
 7         {
 8             //啓動計時器
 9             Watch.Start();
10 
11             const string url1 = "http://www.cnblogs.com/";
12             const string url2 = "http://www.cnblogs.com/liqingwen/";
13 
14             //兩次調用 CountCharacters 方法(下載某網站內容,並統計字符的個數)
15             var result1 = CountCharacters(1, url1);
16             var result2 = CountCharacters(2, url2);
17 
18             //三次調用 ExtraOperation 方法(主要是經過拼接字符串達到耗時操做)
19             for (var i = 0; i < 3; i++)
20             {
21                 ExtraOperation(i + 1);
22             }
23 
24             //控制檯輸出
25             Console.WriteLine($"{url1} 的字符個數:{result1}");
26             Console.WriteLine($"{url2} 的字符個數:{result2}");
27 
28             Console.Read();
29         }
30 
31         /// <summary>
32         /// 統計字符個數
33         /// </summary>
34         /// <param name="id"></param>
35         /// <param name="address"></param>
36         /// <returns></returns>
37         private static int CountCharacters(int id, string address)
38         {
39             var wc = new WebClient();
40             Console.WriteLine($"開始調用 id = {id}:{Watch.ElapsedMilliseconds} ms");
41 
42             var result = wc.DownloadString(address);
43             Console.WriteLine($"調用完成 id = {id}:{Watch.ElapsedMilliseconds} ms");
44 
45             return result.Length;
46         }
47 
48         /// <summary>
49         /// 額外操做
50         /// </summary>
51         /// <param name="id"></param>
52         private static void ExtraOperation(int id)
53         {
54             //這裏是經過拼接字符串進行一些相對耗時的操做
55             var s = "";
56 
57             for (var i = 0; i < 6000; i++)
58             {
59                 s += i;
60             }
61 
62             Console.WriteLine($"id = {id} 的 ExtraOperation 方法完成:{Watch.ElapsedMilliseconds} ms");
63         }
64     }

複製代碼

     圖1-1 運行的效果圖,以毫秒(ms)爲單位

 

  【備註】通常來講,直接拼接字符串是一種比較耗性能的手段,若是對字符串拼接有性能要求的話應該使用 StringBuilder。

  【注意】每次運行的結果可能不一樣。無論哪次調試,絕大部分時間都浪費前兩次調用(CountCharacters 方法),即在等待網站的響應上。

 

  圖1-2 根據執行結果所畫的時間軸

 

     有人曾幻想着這樣提升性能的方法:在調用 A 方法時,不等它執行完,直接執行 B 方法,而後等 A 方法執行完成再處理。

     C# 的 async/await 就能夠容許咱們這麼弄。

複製代碼

 1     class Program
 2     {
 3         //建立計時器
 4         private static readonly Stopwatch Watch = new Stopwatch();
 5 
 6         private static void Main(string[] args)
 7         {
 8             //啓動計時器
 9             Watch.Start();
10 
11             const string url1 = "http://www.cnblogs.com/";
12             const string url2 = "http://www.cnblogs.com/liqingwen/";
13 
14             //兩次調用 CountCharactersAsync 方法(異步下載某網站內容,並統計字符的個數)
15             Task<int> t1 = CountCharactersAsync(1, url1);
16             Task<int> t2 = CountCharactersAsync(2, url2);
17 
18             //三次調用 ExtraOperation 方法(主要是經過拼接字符串達到耗時操做)
19             for (var i = 0; i < 3; i++)
20             {
21                 ExtraOperation(i + 1);
22             }
23 
24             //控制檯輸出
25             Console.WriteLine($"{url1} 的字符個數:{t1.Result}");
26             Console.WriteLine($"{url2} 的字符個數:{t2.Result}");
27 
28             Console.Read();
29         }
30 
31         /// <summary>
32         /// 統計字符個數
33         /// </summary>
34         /// <param name="id"></param>
35         /// <param name="address"></param>
36         /// <returns></returns>
37         private static async Task<int> CountCharactersAsync(int id, string address)
38         {
39             var wc = new WebClient();
40             Console.WriteLine($"開始調用 id = {id}:{Watch.ElapsedMilliseconds} ms");
41 
42             var result = await wc.DownloadStringTaskAsync(address);
43             Console.WriteLine($"調用完成 id = {id}:{Watch.ElapsedMilliseconds} ms");
44 
45             return result.Length;
46         }
47 
48         /// <summary>
49         /// 額外操做
50         /// </summary>
51         /// <param name="id"></param>
52         private static void ExtraOperation(int id)
53         {
54             //這裏是經過拼接字符串進行一些相對耗時的操做
55             var s = "";
56 
57             for (var i = 0; i < 6000; i++)
58             {
59                 s += i;
60             }
61 
62             Console.WriteLine($"id = {id} 的 ExtraOperation 方法完成:{Watch.ElapsedMilliseconds} ms");
63         }
64     }

複製代碼

這是修改後的代碼

 圖1-3 修改後的執行結果圖

圖1-4 根據加入異步後的執行結果畫的時間軸。

 

  咱們觀察時間軸發現,新版代碼比舊版快了很多(因爲網絡波動的緣由,極可能會出現耗時比以前長的狀況)。這是因爲 ExtraOperation 方法的數次調用是在 CountCharactersAsync 方法調用時等待響應的過程當中進行的。全部的工做都是在主線程中完成的,沒有建立新的線程。

 

  【改動分析】只改了幾個細節的地方,直接展開代碼的話可能看不出來,改動以下:

   

 圖1-5

  圖1-6

 

  ①從 Main 方法執行到 CountCharactersAsync(1, url1) 方法時,該方法會當即返回,而後纔會調用它內部的方法開始下載內容。該方法返回的是一個 Task<int> 類型的佔位符對象,表示計劃進行的工做。這個佔位符最終會返回 int 類型的值。

  ②這樣就能夠沒必要等 CountCharactersAsync(1, url1) 方法執行完成就能夠繼續進行下一步操做。到執行 CountCharactersAsync(2, url2)  方法時,跟 ① 同樣返回 Task<int> 對象。

  ③而後,Main 方法繼續執行三次 ExtraOperation 方法,同時兩次 CountCharactersAsync 方法依然在持續工做 。

  ④t1.Result 和 t2.Result 是指從 CountCharactersAsync 方法調用的 Task<int> 對象取結果,若是尚未結果的話,將阻塞,直有結果返回爲止。

 

2、async/await 結構

     先解析一下專業名詞:

     同步方法:一個程序調用某個方法,等到其執行完成以後才進行下一步操做。這也是默認的形式。

     異步方法:一個程序調用某個方法,在處理完成以前就返回該方法。經過 async/await 咱們就能夠實現這種類型的方法。

 

     async/await 結構可分紅三部分:

     (1)調用方法:該方法調用異步方法,而後在異步方法執行其任務的時候繼續執行;

     (2)異步方法:該方法異步執行工做,而後馬上返回到調用方法;

     (3)await 表達式:用於異步方法內部,指出須要異步執行的任務。一個異步方法能夠包含多個 await 表達式(不存在 await 表達式的話 IDE 會發出警告)。

 

  如今咱們來分析一下示例。

  圖2-1

 

 3、What’s 異步方法

     異步方法:在執行完成前當即返回調用方法,在調用方法繼續執行的過程當中完成任務。

     語法分析:

     (1)關鍵字:方法頭使用 async 修飾。

     (2)要求:包含 N(N>0) 個 await 表達式(不存在 await 表達式的話 IDE 會發出警告),表示須要異步執行的任務。

     (3)返回類型:只能返回 3 種類型(void、Task 和 Task<T>)。Task 和 Task<T> 標識返回的對象會在未來完成工做,表示調用方法和異步方法能夠繼續執行。

     (4)參數:數量不限,但不能使用 out 和 ref 關鍵字。

     (5)命名約定:方法後綴名應以 Async 結尾。

     (6)其它:匿名方法和 Lambda 表達式也能夠做爲異步對象;async 是一個上下文關鍵字;關鍵字 async 必須在返回類型前。

 

圖3-1 異步方法的簡單結構圖

 

小結

  1.解析了進程和線程的概念

  2.異步的簡單用法

  3.async/await 結構體

  4.異步方法語法結構

 

走進異步編程的世界 - 剖析異步方法(上)

  這是上篇《走進異步編程的世界 - 開始接觸 async/await 異步編程》(入門)的第二章內容,主要是與你們共同深刻探討下異步方法。

  本文要求瞭解委託的使用。

 

目錄

 

介紹異步方法

     異步方法:在執行完成前當即返回調用方法,在調用方法繼續執行的過程當中完成任務。

     語法分析:

     (1)關鍵字:方法頭使用 async 修飾。

     (2)要求:包含 N(N>0) 個 await 表達式(不存在 await 表達式的話 IDE 會發出警告),表示須要異步執行的任務。【備註】感謝 czcz1024 的修正與補充:沒有的話,就和普通方法同樣執行了。

     (3)返回類型:只能返回 3 種類型(void、Task 和 Task<T>)。Task 和 Task<T> 標識返回的對象會在未來完成工做,表示調用方法和異步方法能夠繼續執行。

     (4)參數:數量不限。但不能使用 out 和 ref 關鍵字。

     (5)命名約定:方法後綴名應以 Async 結尾。

     (6)其它:匿名方法和 Lambda 表達式也能夠做爲異步對象;async 是一個上下文關鍵字;關鍵字 async 必須在返回類型前。

 

圖1 異步方法的簡單結構圖

  關於 async 關鍵字:

  ①在返回類型以前包含 async 關鍵字

  ②它只是標識該方法包含一個或多個 await 表達式,即,它自己不建立異步操做。

  ③它是上下文關鍵字,便可做爲變量名。

 

  如今先來簡單分析一下這三種返回值類型:void、Task 和 Task<T>

  (1)Task<T>:調用方法要從調用中獲取一個 T 類型的值,異步方法的返回類型就必須是Task<T>。調用方法從 Task 的 Result 屬性獲取的就是 T 類型的值。

 1         private static void Main(string[] args)
 2         {
 3             Task<int> t = Calculator.AddAsync(1, 2);
 4 
 5             //一直在幹活
 6 
 7             Console.WriteLine($"result: {t.Result}");
 8 
 9             Console.Read();
10         }

Program.cs

 1     internal class Calculator
 2     {
 3         private static int Add(int n, int m)
 4         {
 5             return n + m;
 6         }
 7 
 8         public static async Task<int> AddAsync(int n, int m)
 9         {
10             int val = await Task.Run(() => Add(n, m));
11 
12             return val;
13         }
14     }

View Code

圖2

圖3

 

  (2)Task:調用方法不須要從異步方法中取返回值,可是但願檢查異步方法的狀態,那麼能夠選擇能夠返回 Task 類型的對象。不過,就算異步方法中包含 return 語句,也不會返回任何東西。

 1         private static void Main(string[] args)
 2         {
 3             Task t = Calculator.AddAsync(1, 2);
 4 
 5             //一直在幹活
 6 
 7             t.Wait();
 8             Console.WriteLine("AddAsync 方法執行完成");
 9 
10             Console.Read();
11         }

Program.cs

 1     internal class Calculator
 2     {
 3         private static int Add(int n, int m)
 4         {
 5             return n + m;
 6         }
 7 
 8         public static async Task AddAsync(int n, int m)
 9         {
10             int val = await Task.Run(() => Add(n, m));
11             Console.WriteLine($"Result: {val}");
12         }
13     }

View Code

 

 

圖4

圖5

     

  (3)void:調用方法執行異步方法,但又不須要作進一步的交互。 

 1         private static void Main(string[] args)
 2         {
 3             Calculator.AddAsync(1, 2);
 4 
 5             //一直在幹活
 6 
 7             Thread.Sleep(1000); //掛起1秒鐘
 8             Console.WriteLine("AddAsync 方法執行完成");
 9 
10             Console.Read();
11         }

Program.cs

 1     internal class Calculator
 2     {
 3         private static int Add(int n, int m)
 4         {
 5             return n + m;
 6         }
 7 
 8         public static async void AddAsync(int n, int m)
 9         {
10             int val = await Task.Run(() => Add(n, m));
11             Console.WriteLine($"Result: {val}");
12         }
13     }

Calculator.cs

圖6

圖7

 

1、控制流

     異步方法的結構可拆分紅三個不一樣的區域:

     (1)表達式以前的部分:從方法頭到第一個 await 表達式之間的全部代碼。

     (2)await 表達式:將被異步執行的代碼。

     (3)表達式以後的部分:await 表達式的後續部分。

 

  圖1-1

 

  該異步方法執行流程:從await表達式以前的地方開始,同步執行到第一個 await,標識着第一部分執行結束,通常來講此時 await 工做還沒完成。當await 任務完成後,該方法將繼續同步執行後續部分。在執行的後續部分中,若是依然存在 await,就重複上述過程。

  當到達 await 表達式時,線程將從異步方法返回到調用方法。若是異步方法的返回類型爲 Task 或 Task<T>,會建立一個 Task 對象,標識須要異步完成的任務,而後將 Task 返回來調用方法。

 

  圖1-2

  異步方法的控制流:

  ①異步執行 await 表達式的空閒任務。

  ②await 表達式執行完成,繼續執行後續部分。如再遇到 await 表達式,按相同狀況進行處理。

  ③到達末尾或遇到 return 語句時,根據返回類型能夠分三種狀況:

    a.void:退出控制流。

    b.Task:設置 Task 的屬性並退出。

    c.Task<T>:設置 Task 的屬性和返回值(Result 屬性)並退出。

  ④同時,調用方法將繼續執行,從異步方法獲取 Task 對象。須要值的時候,會暫停等到 Task 對象的 Result 屬性被賦值纔會繼續執行。

 

  【難點】

  ①第一次遇到 await 所返回對象的類型。這個返回類型就是同步方法頭的返回類型,跟 await 表達式的返回值沒有關係。

  ②到達異步方法的末尾或遇到 return 語句,它並無真正的返回一個值,而是退出了該方法。

 

2、await 表達式

  await 表達式指定了一個異步執行的任務。默認狀況,該任務在當前線程異步執行

  每個任務就是一個 awaitable 類的實例。awaitable 類型指包含 GetAwaiter() 方法的類型。

  實際上,你並不須要構建本身的 awaitable,通常只須要使用 Task 類,它就是 awaitable。

  最簡單的方式是在方法中使用 Task.Run() 來建立一個 Task【注意】它是在不一樣的線程上執行方法。

 

  讓咱們一塊兒來看看示例。

 1     internal class Program
 2     {
 3         private static void Main(string[] args)
 4         {
 5             var t = Do.GetGuidAsync();
 6             t.Wait();
 7 
 8             Console.Read();
 9         }
10 
11 
12         private class Do
13         {
14             /// <summary>
15             /// 獲取 Guid
16             /// </summary>
17             /// <returns></returns>
18             private static Guid GetGuid()   //與Func<Guid> 兼容
19             {
20                 return Guid.NewGuid();
21             }
22 
23             /// <summary>
24             /// 異步獲取 Guid
25             /// </summary>
26             /// <returns></returns>
27             public static async Task GetGuidAsync()
28             {
29                 var myFunc = new Func<Guid>(GetGuid);
30                 var t1 = await Task.Run(myFunc);
31 
32                 var t2 = await Task.Run(new Func<Guid>(GetGuid));
33 
34                 var t3 = await Task.Run(() => GetGuid());
35 
36                 var t4 = await Task.Run(() => Guid.NewGuid());
37 
38                 Console.WriteLine($"t1: {t1}");
39                 Console.WriteLine($"t2: {t2}");
40                 Console.WriteLine($"t3: {t3}");
41                 Console.WriteLine($"t4: {t4}");
42             }
43         }
44     }

View Code

圖2-1

圖2-2

   上面 4 個 Task.Run() 都是採用了 Task Run(Func<TReturn> func) 形式來直接或間接調用 Guid.NewGuid()。

 

  Task.Run() 支持 4 中不一樣的委託類型所表示的方法:Action、Func<TResult>、Func<Task> 和 Func<Task<TResult>>

 1     internal class Program
 2     {
 3         private static void Main(string[] args)
 4         {
 5             var t = Do.GetGuidAsync();
 6             t.Wait();
 7 
 8             Console.Read();
 9         }
10 
11         private class Do
12         {
13             public static async Task GetGuidAsync()
14             {
15                 await Task.Run(() => { Console.WriteLine(Guid.NewGuid()); });   //Action
16 
17                 Console.WriteLine(await Task.Run(() => Guid.NewGuid()));    //Func<TResult>
18 
19                 await Task.Run(() => Task.Run(() => { Console.WriteLine(Guid.NewGuid()); }));   //Func<Task>
20 
21                 Console.WriteLine(await Task.Run(() => Task.Run(() => Guid.NewGuid())));    //Func<Task<TResult>>
22             }
23         }
24     }

View Code

圖2-3 Task.Run() 方法的重載

 

3、How 取消異步操做

   CancellationToken 和 CancellationTokenSource 這兩個類容許你終止執行異步方法。

  (1)CancellationToken 對象包含任務是否被取消的信息;若是該對象的屬性 IsCancellationRequested 爲 true,任務需中止操做並返回;該對象操做是不可逆的,且只能使用(修改)一次,即該對象內的 IsCancellationRequested 屬性被設置後,就不能改動。

  (2)CancellationTokenSource 可建立 CancellationToken 對象,調用 CancellationTokenSource 對象的 Cancel 方法,會使該對象的 CancellationToken 屬性 IsCancellationRequested 設置爲 true。

  【注意】調用 CancellationTokenSource 對象的 Cancel 方法,並不會執行取消操做,而是會將該對象的 CancellationToken 屬性 IsCancellationRequested 設置爲 true。

 

  示例

 1     internal class Program
 2     {
 3         private static void Main(string[] args)
 4         {
 5             CancellationTokenSource source = new CancellationTokenSource();
 6             CancellationToken token = source.Token;
 7 
 8             var t = Do.ExecuteAsync(token);
 9 
10             //Thread.Sleep(3000);   //掛起 3 秒
11             //source.Cancel();    //傳達取消請求
12 
13             t.Wait(token);  //等待任務執行完成
14             Console.WriteLine($"{nameof(token.IsCancellationRequested)}: {token.IsCancellationRequested}");
15 
16             Console.Read();
17         }
18 
19 
20     }
21 
22     internal class Do
23     {
24         /// <summary>
25         /// 異步執行
26         /// </summary>
27         /// <param name="token"></param>
28         /// <returns></returns>
29         public static async Task ExecuteAsync(CancellationToken token)
30         {
31             if (token.IsCancellationRequested)
32             {
33                 return;
34             }
35 
36             await Task.Run(() => CircleOutput(token), token);
37         }
38 
39         /// <summary>
40         /// 循環輸出
41         /// </summary>
42         /// <param name="token"></param>
43         private static void CircleOutput(CancellationToken token)
44         {
45             Console.WriteLine($"{nameof(CircleOutput)} 方法開始調用:");
46 
47             const int num = 5;
48             for (var i = 0; i < num; i++)
49             {
50                 if (token.IsCancellationRequested)  //監控 CancellationToken
51                 {
52                     return;
53                 }
54 
55                 Console.WriteLine($"{i + 1}/{num} 完成");
56                 Thread.Sleep(1000);
57             }
58         }
59     }

View Code

圖3-1

圖3-2 註釋兩行代碼

圖3-3:圖3-1和圖3-2的執行結果(註釋兩行代碼)

  上圖是不調用 Cancel() 方法的結果圖,不會取消任務的執行。

 

  下圖在 3 秒後調用 Cancel() 方法取消任務的執行:

圖3-4:去掉註釋

圖3-5:圖3-1和圖3-4的執行結果(去掉註釋)

 

小結

  • 介紹異步方法的語法、三種不一樣的返回值類型(void、Task 和 Task<T>)和控制流程等。
  • 簡單經常使用的異步執行方式:Task.Run()。【注意】它是在不一樣的線程上執行方法。
  • 如何取消異步操做。

 

 

走進異步編程的世界 - 剖析異步方法(下)

 

  感謝你們的支持,這是昨天發佈《走進異步編程的世界 - 剖析異步方法(上)》的補充篇。

 

目錄

  

1、異常處理

  await 表達式也可使用 try...catch...finally 結構。

複製代碼

 1     internal class Program
 2     {
 3         private static void Main(string[] args)
 4         {
 5             var t = DoExceptionAsync();
 6             t.Wait();
 7 
 8             Console.WriteLine($"{nameof(t.Status)}: {t.Status}");   //任務狀態
 9             Console.WriteLine($"{nameof(t.IsCompleted)}: {t.IsCompleted}");     //任務完成狀態標識
10             Console.WriteLine($"{nameof(t.IsFaulted)}: {t.IsFaulted}");     //任務是否有未處理的異常標識
11 
12             Console.Read();
13         }
14 
15         /// <summary>
16         /// 異常操做
17         /// </summary>
18         /// <returns></returns>
19         private static async Task DoExceptionAsync()
20         {
21             try
22             {
23                 await Task.Run(() => { throw new Exception(); });
24             }
25             catch (Exception)
26             {
27                 Console.WriteLine($"{nameof(DoExceptionAsync)} 出現異常!");
28             }
29         }
30     }

複製代碼

圖1-1

  【分析】await 表達式位於 try 塊中,按普通的方式處理異常。可是,爲何圖中的狀態(Status)、是否完成標識(IsCompleted)和是否失敗標識(IsFaulted)分別顯示:運行完成(RanToCompletion) 、已完成(True) 和 未失敗(False) 呢?由於:任務沒有被取消,而且異常都已經處理完成!

 

2、在調用方法中同步等待任務

  調用方法可能在某個時間點上須要等待某個特殊的 Task 對象完成,才執行後面的代碼。此時,能夠採用實例方法 Wait 。

複製代碼

 1     internal class Program
 2     {
 3         private static void Main(string[] args)
 4         {
 5             var t = CountCharactersAsync("http://www.cnblogs.com/liqingwen/");
 6 
 7             t.Wait();   //等待任務結束
 8             Console.WriteLine($"Result is {t.Result}");
 9 
10             Console.Read();
11         }
12 
13         /// <summary>
14         /// 統計字符數量
15         /// </summary>
16         /// <param name="address"></param>
17         /// <returns></returns>
18         private static async Task<int> CountCharactersAsync(string address)
19         {
20             var result = await Task.Run(() => new WebClient().DownloadStringTaskAsync(address));
21             return result.Length;
22         }
23     }

複製代碼

圖2-1

 

  Wait() 適合用於單一 Task 對象,若是想操做一組對象,可採用 Task 的兩個靜態方法 WaitAll() 和 WaitAny() 。

複製代碼

 1     internal class Program
 2     {
 3         private static int time = 0;
 4         private static void Main(string[] args)
 5         {
 6             var t1 = GetRandomAsync(1);
 7             var t2 = GetRandomAsync(2);
 8 
 9             //IsCompleted 任務完成標識
10             Console.WriteLine($"t1.{nameof(t1.IsCompleted)}: {t1.IsCompleted}");    
11             Console.WriteLine($"t2.{nameof(t2.IsCompleted)}: {t2.IsCompleted}");
12 
13             Console.Read();
14         }
15 
16         /// <summary>
17         /// 獲取一個隨機數
18         /// </summary>
19         /// <param name="id"></param>
20         /// <returns></returns>
21         private static async Task<int> GetRandomAsync(int id)
22         {
23             var num = await Task.Run(() =>
24             {
25                 time++;
26                 Thread.Sleep(time * 100);
27                 return new Random().Next();
28             });
29 
30             Console.WriteLine($"{id} 已經調用完成");
31             return num;
32         }
33     }

複製代碼

圖2-2 兩個任務的 IsCompleted 屬性都顯示未完成

 

  如今,在 Main() 方法中新增兩行代碼(6 和 7 兩行),嘗試調用 WaitAll() 方法。

複製代碼

 1         private static void Main(string[] args)
 2         {
 3             var t1 = GetRandomAsync(1);
 4             var t2 = GetRandomAsync(2);
 5 
 6             Task<int>[] tasks = new Task<int>[] { t1, t2 };
 7             Task.WaitAll(tasks);    //等待任務所有完成,才繼續執行
 8 
 9             //IsCompleted 任務完成標識
10             Console.WriteLine($"t1.{nameof(t1.IsCompleted)}: {t1.IsCompleted}");    
11             Console.WriteLine($"t2.{nameof(t2.IsCompleted)}: {t2.IsCompleted}");
12 
13             Console.Read();
14         }

複製代碼

圖2-3 兩個任務的 IsCompleted 屬性都顯示 True

 

  如今,再次將第 7 行改動一下,調用 WaitAny() 方法試試。 

複製代碼

 1         private static void Main(string[] args)
 2         {
 3             var t1 = GetRandomAsync(1);
 4             var t2 = GetRandomAsync(2);
 5 
 6             Task<int>[] tasks = new Task<int>[] { t1, t2 };
 7             Task.WaitAny(tasks);    //等待任一 Task 完成,才繼續執行
 8 
 9             //IsCompleted 任務完成標識
10             Console.WriteLine($"t1.{nameof(t1.IsCompleted)}: {t1.IsCompleted}");    
11             Console.WriteLine($"t2.{nameof(t2.IsCompleted)}: {t2.IsCompleted}");
12 
13             Console.Read();
14         }

複製代碼

圖2-4 有一個任務的 IsCompleted 屬性顯示 True (完成) 就繼續執行

 

3、在異步方法中異步等待任務

  上節說的是如何使用 WaitAll() 和 WaitAny() 同步地等待 Task 完成。此次咱們使用 Task.WhenAll() 和 Task.WhenAny()  在異步方法中異步等待任務。

複製代碼

 1     internal class Program
 2     {
 3         private static int time = 0;
 4 
 5         private static void Main(string[] args)
 6         {
 7             var t = GetRandomAsync();
 8 
 9             Console.WriteLine($"t.{nameof(t.IsCompleted)}: {t.IsCompleted}");
10             Console.WriteLine($"Result: {t.Result}");
11 
12             Console.Read();
13         }
14 
15         /// <summary>
16         /// 獲取一個隨機數
17         /// </summary>
18         /// <param name="id"></param>
19         /// <returns></returns>
20         private static async Task<int> GetRandomAsync()
21         {
22             time++;
23             var t1 = Task.Run(() =>
24             {
25                 Thread.Sleep(time * 100);
26                 return new Random().Next();
27             });
28 
29             time++;
30             var t2 = Task.Run(() =>
31             {
32                 Thread.Sleep(time * 100);
33                 return new Random().Next();
34             });
35 
36             //異步等待集合內的 Task 都完成,才進行下一步操做
37             await Task.WhenAll(new List<Task<int>>() { t1, t2 });
38 
39             Console.WriteLine($"    t1.{nameof(t1.IsCompleted)}: {t1.IsCompleted}");
40             Console.WriteLine($"    t2.{nameof(t2.IsCompleted)}: {t2.IsCompleted}");
41 
42             return t1.Result + t2.Result;
43         }
44     }

複製代碼

圖3-1 調用 WhenAll()  方法

  【注意】WhenAll() 異步等待集合內的 Task 都完成,不會佔用主線程的時間

 

   如今,咱們把 GetRandomAsync() 方法內的 WhenAll() 方法替換成 WhenAny(),而且增大一下線程掛起時間,最終改動以下:

複製代碼

 1         private static async Task<int> GetRandomAsync()
 2         {
 3             time++;
 4             var t1 = Task.Run(() =>
 5             {
 6                 Thread.Sleep(time * 100);
 7                 return new Random().Next();
 8             });
 9 
10             time++;
11             var t2 = Task.Run(() =>
12             {
13                 Thread.Sleep(time * 500);   //這裏由 100 改成 500,否則看不到效果
14                 return new Random().Next();
15             });
16 
17             //異步等待集合內的 Task 都完成,才進行下一步操做
18             //await Task.WhenAll(new List<Task<int>>() { t1, t2 });
19             await Task.WhenAny(new List<Task<int>>() { t1, t2 });
20 
21             Console.WriteLine($"    t1.{nameof(t1.IsCompleted)}: {t1.IsCompleted}");
22             Console.WriteLine($"    t2.{nameof(t2.IsCompleted)}: {t2.IsCompleted}");
23 
24             return t1.Result + t2.Result;
25         }

複製代碼

圖3-2 調用 WhenAny() 方法

 

4、Task.Delay() 暫停執行

  Task.Delay() 方法會建立一個 Task 對象,該對象將暫停其在線程中的處理,並在必定時間以後完成。和 Thread.Sleep 不一樣的是,它不會阻塞線程,意味着線程能夠繼續處理其它工做。

複製代碼

 1     internal class Program
 2     {
 3         private static void Main(string[] args)
 4         {
 5             Console.WriteLine($"{nameof(Main)} - start.");
 6             DoAsync();
 7             Console.WriteLine($"{nameof(Main)} - end.");
 8 
 9             Console.Read();
10         }
11 
12         private static async void DoAsync()
13         {
14             Console.WriteLine($"    {nameof(DoAsync)} - start.");
15 
16             await Task.Delay(500);
17 
18             Console.WriteLine($"    {nameof(DoAsync)} - end.");
19         }
20     }

複製代碼

圖4-1

相關文章
相關標籤/搜索