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

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

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

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

 

目錄

 

介紹異步方法

      異步方法:在執行完成前當即返回調用方法,在調用方法繼續執行的過程當中完成任務。
     語法分析:
     (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 關鍵字:post

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

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

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

 

  如今先來簡單分析一下這三種返回值類型: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()。【注意】它是在不一樣的線程上執行方法。
  • 如何取消異步操做。

 

傳送門

  入門:《開始接觸 async/await 異步編程

  補充篇:《走進異步編程的世界 - 剖析異步方法(下)

  GUI 篇:《走進異步編程的世界 - 在 GUI 中執行異步操做

 


原文連接:http://www.cnblogs.com/liqingwen/p/5844095.html

 【參考】《Illustrated C# 2012》

相關文章
相關標籤/搜索