1、異步方法返回類型html
只能返回3種類型(void、Task和Task<T>)。異步
1.一、void返回類型:調用方法執行異步方法,但又不須要作進一步的交互。async
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}"); } }
運行結果以下:ide
1.二、Task返回類型:調用方法不須要從異步方法中取返回值,可是但願檢查異步方法的狀態,那麼能夠選擇能夠返回Task類型的對象。不過,就算異步方法中包含函數
return語句,也不會返回任何東西。post
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}"); } }
運行結果以下:學習
1.三、Task<T>返回類型:調用方法要從調用中獲取一個T類型的值,異步方法的返回類型就必須是Task<T>。調用方法從Task的Result屬性獲取的就是T類型的值。ui
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; } }
運行結果以下:this
2、異步方法控制流atom
異步方法的控制流:
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語句,它並無真正的返回一個值,而是退出了該方法。
3、異步方法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 #region async & await入門二之await如何實現異步 AwaitAchieveAsync(); Console.Read(); #endregion } /// <summary> /// AwaitAchieveAsync異步方法 /// </summary> public static async void AwaitAchieveAsync() { var j = await 3; Console.WriteLine(j); } }
運行結果以下:
這樣咱們就能看出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}"); } }
上面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>> }
運行結果以下:
4、異步方法暫停
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."); } }
運行結果以下:
五、異步方法取消
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); } } }
運行結果以下:
六:異步方法異常處理
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)} 出現異常。"); } } }
後記:1、4、5、六也算是C#線程學習筆記七:Task詳細用法的一些補充,其它的用法與筆記七的用法差很少的,這裏就再也不贅述了。
參考自:
http://www.javashuo.com/article/p-yokamgwd-du.html
https://www.cnblogs.com/TianFang/archive/2012/09/21/2696769.html