前言:前面有篇從應用層面上面介紹了下多線程的幾種用法,有博友就說到了async, await等新語法。確實,沒有異步的多線程是單調的、乏味的,async和await是出如今C#5.0以後,它的出現給了異步並行變成帶來了很大的方便。異步編程涉及到的東西仍是比較多,本篇仍是先介紹下async和await的原理及簡單實現。html
C#基礎系列目錄:面試
以前的那篇 C#基礎系列——多線程的常見用法詳解 就講到了多線程new Thread()的方式對於有返回值類型的委託是沒有解決方案的,若是須要返回值,必需要依靠異步的方式。瞭解異步以前,咱們先來看看Thread對象的升級版本Task對象:編程
一、Task對象的前世此生:Task對象是.Net Framework 4.0以後出現的異步編程的一個重要對象。在必定程度上來講,Task對象能夠理解Thread對象的一個升級產品。既然是升級產品,那它確定有他的優點,好比咱們上面Thread對象不能解決的問題:對於有返回值類型的委託。Task對象就能簡單的解決。設計模式
static void Main(string[] args) { Console.WriteLine("執行GetReturnResult方法前的時間:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); var strRes = Task.Run<string>(() => { return GetReturnResult(); });//啓動Task執行方法 Console.WriteLine("執行GetReturnResult方法後的時間:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.WriteLine(strRes.Result);//獲得方法的返回值 Console.WriteLine("獲得結果後的時間:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.ReadLine(); } static string GetReturnResult() { Thread.Sleep(2000); return "我是返回值"; }
先來看結果:多線程
從結果分析可知在執行var strRes = Task.Run<string>(() => { return GetReturnResult(); })這一句後,主線程並無阻塞去執行GetReturnResult()方法,而是開啓了另外一個線程去執行GetReturnResult()方法。直到執行strRes.Result這一句的時候主線程纔會等待GetReturnResult()方法執行完畢。爲何說是開啓了另外一個線程,咱們經過線程ID能夠看得更清楚:異步
static void Main(string[] args) { Console.WriteLine("執行GetReturnResult方法前的時間:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); var strRes = Task.Run<string>(() => { return GetReturnResult(); }); Console.WriteLine("執行GetReturnResult方法後的時間:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.WriteLine("我是主線程,線程ID:" + Thread.CurrentThread.ManagedThreadId); Console.WriteLine(strRes.Result); Console.WriteLine("獲得結果後的時間:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.ReadLine(); } static string GetReturnResult() { Console.WriteLine("我是GetReturnResult裏面的線程,線程ID:" + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(2000); return "我是返回值"; }
結果:async
由此能夠得知,Task.Run<string>(()=>{}).Reslut是阻塞主線程的,由於主線程要獲得返回值,必需要等方法執行完成。異步編程
Task對象的用法以下:函數
//用法一 Task task1 = new Task(new Action(MyAction)); //用法二 Task task2 = new Task(delegate { MyAction(); }); //用法三 Task task3 = new Task(() => MyAction()); Task task4 = new Task(() => { MyAction(); }); task1.Start(); task2.Start(); task3.Start(); task4.Start();
由上可知,Task對象的構造函數傳入的是一個委託,既然能傳入Action類型的委託,可想而知Action的16中類型的參數又能夠派上用場了。因而乎Task對象參數的傳遞就不用多說了吧。詳見 C#基礎系列——委託和設計模式(一)裏面Action委託的用法。post
二、初識 async & await。
static void Main(string[] args) { Console.WriteLine("我是主線程,線程ID:{0}", Thread.CurrentThread.ManagedThreadId); TestAsync(); Console.ReadLine(); } static async Task TestAsync() { Console.WriteLine("調用GetReturnResult()以前,線程ID:{0}。當前時間:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); var name = GetReturnResult(); Console.WriteLine("調用GetReturnResult()以後,線程ID:{0}。當前時間:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.WriteLine("獲得GetReturnResult()方法的結果:{0}。當前時間:{1}", await name, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); } static async Task<string> GetReturnResult() { Console.WriteLine("執行Task.Run以前, 線程ID:{0}", Thread.CurrentThread.ManagedThreadId); return await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine("GetReturnResult()方法裏面線程ID: {0}", Thread.CurrentThread.ManagedThreadId); return "我是返回值"; }); }
結果:
咱們來看看程序的執行過程:
由上面的結果能夠獲得以下結論:
(1)在async標識的方法體裏面,若是沒有await關鍵字的出現,那麼這種方法和調用普通的方法沒什麼區別。
(2)在async標識的方法體裏面,在await關鍵字出現以前,仍是主線程順序調用的,直到await關鍵字的出現纔會出現線程阻塞。
(3)await關鍵字能夠理解爲等待方法執行完畢,除了能夠標記有async關鍵字的方法外,還能標記Task對象,表示等待該線程執行完畢。因此await關鍵字並非針對於async的方法,而是針對async方法所返回給咱們的Task。
(4)是否async關鍵字只能標識返回Task對象的方法呢。咱們來試試:
異步方法的返回類型必須爲void、Task或者Task<T>類型。也就是說async要麼是void,要麼和Task關聯。
三、除了await關鍵字,Task對象還有另一種方式等待執行結果。
static async Task TestAsync() { Console.WriteLine("調用GetReturnResult()以前,線程ID:{0}。當前時間:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); var name = GetReturnResult(); Console.WriteLine("調用GetReturnResult()以後,線程ID:{0}。當前時間:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.WriteLine("獲得GetReturnResult()方法的結果:{0}。當前時間:{1}", name.GetAwaiter().GetResult(), DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); }
這樣能夠獲得相同的結果。
name.GetAwaiter()這個方法獲得的是一個TaskAwaiter對象,這個對象表示等待完成的異步任務的對象,並提供結果的參數。因此除了能完成await關鍵字的等待以外,它還能作一些其餘的操做。咱們將TaskAwaiter轉到定義
public struct TaskAwaiter<TResult> : ICriticalNotifyCompletion, INotifyCompletion { public bool IsCompleted { get; } public TResult GetResult(); public void OnCompleted(Action continuation); public void UnsafeOnCompleted(Action continuation); }
IsCompleted:獲取一個值,該值指示異步任務是否已完成。
GetResult():獲得執行的結果。這個方法和await關鍵字效果相同。
OnCompleted():傳入一個委託,在任務執行完成以後執行。
UnsafeOnCompleted():計劃與此 awaiter 相關異步任務的延續操做。
由此能夠看出,await關鍵字實際上就是調用了TaskAwaiter對象的GetResult()方法。