一、非同步方法返回類型 只能返回3種類型(void、Task和Task<T>)。 1.1、void返回類型:調用方法執行非同步方法,但又不需要做進一步的交互。 class Program { static void Main(string[] args) { #region async & await入 ...
一、非同步方法返回類型
只能返回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