一、異步方法回傳型別
只能回傳3種型別(void、Task和Task<T>),
1.1、void回傳型別:呼叫方法執行異步方法,但又不需要做進一步的互動,
class Program { static void Main(string[] args) { #region async & await入門二之void回傳型別 AddAsync(1, 2); Thread.Sleep(1000); Console.WriteLine("AddAsync方法執行完成,"); Console.Read(); #endregion } /// <summary> /// 加法 /// </summary> /// <param name="n"></param> /// <param name="m"></param> /// <returns></returns> private static int Add(int n, int m) { return n + m; } /// <summary> /// 異步加法 /// </summary> /// <param name="n"></param> /// <param name="m"></param> private static async void AddAsync(int n, int m) { int val = await Task.Run(() => Add(n, m)); Console.WriteLine($"Result: {val}"); } }View Code
運行結果如下:

1.2、Task回傳型別:呼叫方法不需要從異步方法中取回傳值,但是希望檢查異步方法的狀態,那么可以選擇可以回傳Task型別的物件,不過,就算異步方法中包含
return陳述句,也不會回傳任何東西,
class Program { static void Main(string[] args) { #region async & await入門二之Task回傳型別 Task task = TaskAddAsync(1, 2); task.Wait(); Console.WriteLine("TaskAddAsync方法執行完成,"); Console.Read(); #endregion } /// <summary> /// 加法 /// </summary> /// <param name="n"></param> /// <param name="m"></param> /// <returns></returns> private static int Add(int n, int m) { return n + m; } /// <summary> /// 異步加法 /// </summary> /// <param name="n"></param> /// <param name="m"></param> /// <returns></returns> private static async Task TaskAddAsync(int n, int m) { int val = await Task.Run(() => Add(n, m)); Console.WriteLine($"Result: {val}"); } }View Code
運行結果如下:

1.3、Task<T>回傳型別:呼叫方法要從呼叫中獲取一個T型別的值,異步方法的回傳型別就必須是Task<T>,呼叫方法從Task的Result屬性獲取的就是T型別的值,
class Program { static void Main(string[] args) { #region async & await入門二之Task<T>回傳型別 Task<int> task = TaskTAddAsync(1, 2); task.Wait(); Console.WriteLine($"Result: {task.Result}"); Console.Read(); #endregion } /// <summary> /// 加法 /// </summary> /// <param name="n"></param> /// <param name="m"></param> /// <returns></returns> private static int Add(int n, int m) { return n + m; } /// <summary> /// 異步加法 /// </summary> /// <param name="n"></param> /// <param name="m"></param> /// <returns></returns> private static async Task<int> TaskTAddAsync(int n, int m) { int val = await Task.Run(() => Add(n, m)); return val; } }View Code
運行結果如下:

二、異步方法控制流

異步方法的控制流:
1)異步執行await運算式的空閑任務,
2)await運算式執行完畢并釋放執行緒,然后從執行緒池中申請新的執行緒繼續執行運算式后續部分,如再遇到await運算式,按相同情況進行處理,
3)到達末尾或遇到return陳述句時,根據回傳型別可以分三種情況:
<1>void:退出控制流,
<2>Task:設定Task的屬性并退出,
<3>Task<T>:設定Task的屬性和回傳值(Result屬性)并退出,
4)呼叫方法將繼續執行,需要注意的是:若呼叫方法需要用到異步方法結果的時候,會暫停等到Task物件的Result屬性被賦值后才會繼續執行,
【難點】
1)第一次遇到await所回傳物件的型別,是同步方法頭的回傳型別,跟await運算式的回傳值沒有關系,
2)到達異步方法的末尾或遇到return陳述句,它并沒有真正的回傳一個值,而是退出了該方法,
三、異步方法await運算式
在大多數的時候,await一般和Task一起使用,用await運算式來指定一個異步執行的任務,以實作更高的靈活性和效率,
可以用于await運算子的物件要求如下:
1)有一個GetAwaiter()方法或擴展方法,它回傳一個實作了INotifyCompletion介面的awaiter物件(或結構)
2)回傳的awaiter物件(或結構)要求實作如下方法:
<1>void OnCompleted(Action continuation)
<2>bool IsCompleted { get ; }
<3>TResult GetResult() //TResult也可以是void型別
下面簡單的介紹一下await運算子是如何實作異步操作的?
例如,對于如下代碼
var j = await 3;
DoContinue(j);
在編譯的時候會被編譯器翻譯為類似如下流程的代碼(注:這個只是功能類似的簡化流程示例,實際并非如此),
var awaiter = 3.GetAwaiter();
var continuation = new Action(() =>
{
var j = awaiter.GetResult();
DoContinue(j);
});
if (awaiter.IsCompleted)
continuation();
else
awaiter.OnCompleted(continuation);
有了這個基礎,我們就可以對一個int型的變數實作await操作了:
/// <summary> /// MyAwaiter類 /// </summary> class MyAwaiter : System.Runtime.CompilerServices.INotifyCompletion { public bool IsCompleted { get { return false; } } public void OnCompleted(Action continuation) { Console.WriteLine("OnCompleted"); ThreadPool.QueueUserWorkItem(_ => { Thread.Sleep(1000); result = 300; continuation(); }); } int result; public int GetResult() { Console.WriteLine("GetResult"); return result; } } /// <summary> /// MyAwaiter類擴展 /// </summary> static class MyAwaiterExtend { public static MyAwaiter GetAwaiter(this int i) { return new MyAwaiter(); } } class Program { static void Main(string[] args) { #region async & await入門二之await如何實作異步 AwaitAchieveAsync(); Console.Read(); #endregion } /// <summary> /// AwaitAchieveAsync異步方法 /// </summary> public static async void AwaitAchieveAsync() { var j = await 3; Console.WriteLine(j); } }View Code
運行結果如下:

這樣我們就能看出await是如何實作call/cc式的異步操作了:
1)編譯器把后續操作封裝為一個Action物件continuation,傳入awaiter的OnCompleted函式并執行,
2)awaiter在OnCompleted函式中執行異步操作,并在異步操作完成后(一般是異步呼叫的回呼函式)執行continuation操作,
3)continuation操作的第一步就是呼叫awaiter.GetResult()獲取異步操作的回傳值,并繼續執行后續操作,
看到這里,相信大家對await的機制已經有了簡單的認識,也就不難理解為什么AsyncTargetingPack能使得.NET 4.0程式也支持await操作了——該庫在
AsyncCompatLibExtensions類中對Task類提供了GetAwaiter擴展函式而已,
以上是通過創建自己的awaitable型別來演示await實作異步的程序,實際上,你并不需要構建自己的awaitable型別,只需要使用Task類即可,每一個任務都是awaitable
類的實體,
下面代碼演示使用Task.Run()來創建一個Task,
class Program { static void Main(string[] args) { #region async & await入門二之使用Task.Run創建Task var task = GetGuidAsync(); task.Wait(); Console.Read(); #endregion } /// <summary> /// 獲取 Guid /// </summary> /// <returns></returns> private static Guid GetGuid() { return Guid.NewGuid(); } /// <summary> /// 異步獲取Guid /// </summary> /// <returns></returns> private static async Task GetGuidAsync() { var myFunc = new Func<Guid>(GetGuid); var t1 = await Task.Run(myFunc); var t2 = await Task.Run(new Func<Guid>(GetGuid)); var t3 = await Task.Run(() => GetGuid()); var t4 = await Task.Run(() => Guid.NewGuid()); Console.WriteLine($"t1: {t1}"); Console.WriteLine($"t2: {t2}"); Console.WriteLine($"t3: {t3}"); Console.WriteLine($"t4: {t4}"); } }View Code
上面4個Task.Run() 都是采用了Task.Run(Func<TResult> func) 形式來直接或間接呼叫Guid.NewGuid(),
運行結果如下:

Task.Run()支持4種不同的委托型別:Action、Func<TResult>、Func<Task> 和 Func<Task<TResult>>
class Program { static void Main(string[] args) { #region async & await入門二之使用Task.Run支持的4種委托型別 var task = GetGuidFrom4Async(); task.Wait(); Console.Read(); #endregion } /// <summary> /// 獲取 Guid /// </summary> /// <returns></returns> private static Guid GetGuid() { return Guid.NewGuid(); } /// <summary> /// 異步獲取Guid(Task.Run支持的4種委托型別) /// </summary> /// <returns></returns> private static async Task GetGuidFrom4Async() { await Task.Run(() => { Console.WriteLine(Guid.NewGuid()); }); //Action Console.WriteLine(await Task.Run(() => Guid.NewGuid())); //Func<TResult> await Task.Run(() => Task.Run(() => { Console.WriteLine(Guid.NewGuid()); })); //Func<Task> Console.WriteLine(await Task.Run(() => Task.Run(() => Guid.NewGuid()))); //Func<Task<TResult>> }View Code
運行結果如下:

四、異步方法暫停
Task.Delay() 與Thread.Sleep不同的是,它不會阻塞執行緒,意味著執行緒可以繼續處理其它作業,
class Program { static void Main(string[] args) { #region async & await入門二之異步方法暫停 Console.WriteLine($"{nameof(Main)} start."); DoAsync(); Console.WriteLine($"{nameof(Main)} end."); Console.Read(); #endregion } /// <summary> /// 異步執行 /// </summary> private static async void DoAsync() { Console.WriteLine($"{nameof(DoAsync)} start."); await Task.Delay(500); Console.WriteLine($"{nameof(DoAsync)} end."); } }View Code
運行結果如下:

五、異步方法取消
CancellationToken和CancellationTokenSource這兩個類允許你終止執行異步方法,
1)CancellationToken物件包含任務是否被取消的資訊,如果該物件的屬性IsCancellationRequested為true,任務需停止操作并回傳,該物件操作是不可逆的,且只能
使用(修改)一次,即該物件內的IsCancellationRequested屬性被設定后,就不能改動,
2)CancellationTokenSource可創建CancellationToken物件,呼叫CancellationTokenSource物件的Cancel方法,會使該物件的CancellationToken屬性
IsCancellationRequested設定為true,
【注意】呼叫CancellationTokenSource物件的Cancel方法,并不會執行取消操作,而是會將該物件的CancellationToken屬性IsCancellationRequested設定為true,
class Program { static void Main(string[] args) { #region async & await入門二之異步方法取消 CancellationTokenSource source = new CancellationTokenSource(); CancellationToken token = source.Token; var task = ExecuteAsync(token); Console.WriteLine($"{nameof(token.IsCancellationRequested)}: {token.IsCancellationRequested}"); Thread.Sleep(6000); //掛起6秒 source.Cancel(); //傳達取消請求 task.Wait(token); //等待任務執行完成 Console.WriteLine($"{nameof(token.IsCancellationRequested)}: {token.IsCancellationRequested}"); Console.Read(); #endregion } /// <summary> /// 異步執行 /// </summary> /// <param name="token"></param> /// <returns></returns> private static async Task ExecuteAsync(CancellationToken token) { if (token.IsCancellationRequested) { return; } await Task.Run(() => CircleOutput(token), token); } /// <summary> /// 回圈輸出 /// </summary> /// <param name="token"></param> private static void CircleOutput(CancellationToken token) { Console.WriteLine($"{nameof(CircleOutput)} 方法開始呼叫:"); const int num = 5; for (var i = 0; i < num; i++) { if (token.IsCancellationRequested) //監控CancellationToken { return; } Console.WriteLine($"{i + 1}/{num} 完成"); Thread.Sleep(1000); } } }View Code
運行結果如下:

六:異步方法例外處理
class Program { static void Main(string[] args) { #region async & await入門二之異步方法例外處理 var task = ExceptionAsync(); task.Wait(); Console.WriteLine($"{nameof(task.Status)}: {task.Status}"); //任務狀態 Console.WriteLine($"{nameof(task.IsCompleted)}: {task.IsCompleted}"); //任務完成狀態標識 Console.WriteLine($"{nameof(task.IsFaulted)}: {task.IsFaulted}"); //任務是否有未處理的例外標識 Console.Read(); #endregion } /// <summary> /// 例外操作 /// </summary> /// <returns></returns> private static async Task ExceptionAsync() { try { await Task.Run(() => { throw new Exception(); }); } catch (Exception) { Console.WriteLine($"{nameof(ExceptionAsync)} 出現例外,"); } } }View Code

后記:一、四、五、六也算是C#執行緒學習筆記七:Task詳細用法的一些補充,其它的用法與筆記七的用法差不多的,這里就不再贅述了,
參考自:
https://www.cnblogs.com/liqingwen/p/5844095.html
https://www.cnblogs.com/TianFang/archive/2012/09/21/2696769.html
https://www.cnblogs.com/liqingwen/p/5866241.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/89676.html
標籤:C#
