一:async若是是用於方法聲明裏,那麼要求這個方法的返回值必須是Task、Task<TResult>、void這三種,並且await出現的地方要求其所在的方法必須是async修飾的方法;閉包
不過void的最好不要用,若是說方法沒有返回值用Task,有返回值用Task<TResult>;異步
先看兩段代碼:async
①測試
static void Main(string[] args) { var task = FooAsync(); // Result的get方法會阻塞當前線程直到task執行完畢 Console.WriteLine(task.Result); Console.WriteLine("Hello World!"); } static async Task<int> FooAsync() {
// await task對象能夠直接獲取task對象執行完畢的返回值,所以await會將Task.Run(..)做爲同步塊; int result = await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"{nameof(FooAsync)}方法耗時3秒執行完畢"); return 8; }); return result; }
②pwa
static void Main(string[] args) { var task = FooAsync(); // Result的get方法會阻塞當前線程直到task執行完畢 Console.WriteLine(task.Result); Console.WriteLine("Hello World!"); }
// TODO 注意,這個地方去掉了async和裏面的await static Task<int> FooAsync() { // 這時候不能用int做爲變量類型了 Task<int> result = Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"{nameof(FooAsync)}方法耗時3秒執行完畢"); return 8; }); return result; }
對於上面的async和await的寫法,那麼async和await關鍵字其實能夠說一點用處也沒有,直接經過Task就能達到徹底如出一轍的效果;線程
接着來看另外一種寫法:對象
static void Main(string[] args) { var task = FooAsync(); // Result的get方法會阻塞當前線程直到task執行完畢 Console.WriteLine(task.Result); Console.WriteLine("Hello World!"); } /** * 注意,Task是後臺任務(由後臺線程執行),而Java的Task,或者說Java默認的線程建立工廠建立的線程是前臺線程 */ static async Task<string> FooAsync() { // 這時候不能用int做爲變量類型了 Task<int> result = Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"{nameof(FooAsync)}方法耗時3秒執行完畢"); return 8; }); int resultValue = await result; return "333N" + resultValue; // TODO 注意這裏多了轉換結果的步驟,用async+await寫這種需求很方便,若是隻用Task那麼就得把Task.Run(...)提高到外部調用的地方纔能方便的實現這個功能; }
這種寫法只用Task<TResult>就不容易實現了,至少寫起來要多蠻多步驟(須要再手動寫個Task<string>對象而後用lambda表達式將await出來的結果轉換爲string返回出來,而後返回這個new出來的Task<string>對象);blog
而後再看另一種寫法get
static void Main(string[] args) { var watch1 = Stopwatch.StartNew(); var watch2 = Stopwatch.StartNew(); Console.WriteLine("Begin"); var task = FooAsync(); watch1.Stop(); Console.WriteLine($"#耗時{watch1.ElapsedMilliseconds}"); // Result的get方法會阻塞當前線程直到task執行完畢 // 這個若是註釋掉,那麼FooAsync則只阻塞主線程2秒左右,若是不註釋下面的代碼,那麼上面的FooAsync()會先阻塞主線程 //Console.WriteLine(task.Result); watch2.Stop(); Console.WriteLine($"耗時{watch2.ElapsedMilliseconds}"); Console.WriteLine("End"); } /** * 注意,Task是後臺任務(由後臺線程執行),而Java的Task,或者說Java默認的線程建立工廠建立的線程是前臺線程(可配置) */ static async Task<int> FooAsync() { Thread.Sleep(2000); // 這一個會阻塞調用者 Console.WriteLine($"{nameof(FooAsync)}方法先同步耗時2秒"); // 這時候不能用int做爲變量類型了 var result = Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"{nameof(FooAsync)}方法耗時3秒執行完畢"); return 8; }); Thread.Sleep(2000); // 重要,這一塊不必定會阻塞調用方(await放在Task.Run那裏) Console.WriteLine($"###{nameof(FooAsync)}方法先同步耗時2秒"); // 注意,若是這個await放到上面的Task.Run(..)那裏會令這一塊的邏輯很奇怪,並且貌似執行是有問題的,都不知道它執行完畢沒有 return await result; }
其實仔細一比較,async和await是能夠不須要的,用Task<TResult>就徹底能夠實現,只不過有部分寫法會稍微麻煩一點點而已;await task;就能夠用task.Result來代替;編譯器
async加await其實就是實現這樣一個功能:
有async的方法,是在告訴編譯器這個方法內部可能存在異步調用,如Task.Run(...); 而後內部對這個異步調用進行一個await(沒有也行可是就失去了async和await帶來的糖); 這個糖的最重要的提如今於外部調用這個方法時,如這個方法叫FooAsync(),調用過程爲: var task = FooAsync(); 若是接下來的代碼沒有相似task.Wait()或task.Result這樣的等待操做,那麼FooAsync()裏面的異步部分對於調用者也是異步的(包括await後面的所有代碼對於調用者都是異步的,這點很重要,如var result = await Task.Run(...);Thread.Sleep(2000);這個Sleep不會阻塞調用者);
async/await實際上是一種相似模板類的技術,有async且內部有await的方法是一種具有不一樣編譯狀況的方法(怎麼編譯要看怎麼調用及使用具備async的方法,如調用者有task.Result是一種使用模式,沒有task.Result或沒有await task或task.Wait..之類的又是另外一種模式);
static void Main(string[] args) { // async/await實際上是一種相似模板類的技術,有async且內部有await的方法是一種具有不一樣編譯狀況的方法; var watch1 = Stopwatch.StartNew(); var watch2 = Stopwatch.StartNew(); Console.WriteLine("Begin"); var task = FooAsync(); watch1.Stop(); Console.WriteLine($"#耗時{watch1.ElapsedMilliseconds}"); // Result的get方法會阻塞當前線程直到task執行完畢 // 這個若是註釋掉,那麼FooAsync則只阻塞主線程2秒左右,若是不註釋下面的代碼,那麼上面的FooAsync()會先阻塞主線程 //Console.WriteLine(task.Result); watch2.Stop(); Console.WriteLine($"耗時{watch2.ElapsedMilliseconds}"); Console.WriteLine("End"); // 這裏能夠經過輸出End後馬上按回車或等幾秒後按回車進行測試; Console.ReadKey(); } /** * 注意,Task是後臺任務(由後臺線程執行),而Java的Task,或者說Java默認的線程建立工廠建立的線程是前臺線程(可配置)
* 甚至編譯器將FooAsync()方法編譯成了FooAsync`1()和FooAsync`2()兩個子方法,而後看調用者是否涉及到同步FooAsync方法結果的操做,有則編譯器給調用方調用的是FooAsync`1(),沒有則是用的FooAsync`2()方法 */ static async Task<int> FooAsync() { Thread.Sleep(2000); // 這一個會阻塞調用者 Console.WriteLine($"{nameof(FooAsync)}方法先同步耗時2秒"); // 注意,await以後的全部代碼是否阻塞調用者取決於調用者怎麼用這個方法,若是調用者是await FooAsync(),那麼這裏以後的全部代碼都是同步的 // TODO 因此async和await和Task<TResult>相比多了一個相似開關同樣的東西,可是這個開關是編譯階段肯定的,有點像模板類用的時候必須給出具體的類型,而這裏是具體的用法; // TODO 編譯時若是調用者有task.Result之類的wait操做,那麼【編譯器】就將FooAsync整個當成一個同步方法,若是沒有相關的操做,編譯器就將這部分及其後面的代碼做爲異步方法處理【應該是經過閉包造成一個新的Task對象】 // TODO 這裏往下的代碼,若是編譯器發現調用方不須要同步,那麼編譯器底層是經過再開一個Task<int>將result和下面的Thread.Sleep(4000)等代碼用閉包再包一層返回這個task對象; var result = await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"{nameof(FooAsync)}方法耗時3秒執行完畢"); return 8; }); // TODO 注意,這一塊是在上面的result執行完後才執行的,即上面先輸出 FooAsync方法耗時3秒執行完畢 而後等待4秒後再輸出 ###FooAsync方法先同步耗時4秒 Thread.Sleep(4000); // 重要,這一塊不必定會阻塞調用方(await放在Task.Run那裏),這一塊是否阻塞調用者看調用者是否須要對返回的Task對象進行wait,且是編譯時決定的; Console.WriteLine($"###{nameof(FooAsync)}方法先同步耗時4秒"); // 注意,若是這個await放到上面的Task.Run(..)那裏會令這一塊的邏輯很奇怪,並且貌似執行是有問題的,都不知道它執行完畢沒有 return result; }
來看進一步的測試:
static void Main(string[] args) { // async/await實際上是一種相似模板類的技術,有async且內部有await的方法是一種具有不一樣編譯狀況的方法; var watch1 = Stopwatch.StartNew(); var watch2 = Stopwatch.StartNew(); Console.WriteLine($"Begin,threadId:{Thread.CurrentThread.ManagedThreadId}"); var task = FooAsync(); watch1.Stop(); Console.WriteLine($"#耗時{watch1.ElapsedMilliseconds}"); // Result的get方法會阻塞當前線程直到task執行完畢 // 這個若是註釋掉,那麼FooAsync則只阻塞主線程2秒左右,若是不註釋下面的代碼,那麼上面的FooAsync()會先阻塞主線程 //Console.WriteLine(task.Result); // TODO flagN watch2.Stop(); Console.WriteLine($"耗時{watch2.ElapsedMilliseconds}"); Console.WriteLine("End"); // 這裏能夠經過輸出End後馬上按回車或等幾秒後按回車進行測試; Console.ReadKey(); } /** * 注意,Task是後臺任務(由後臺線程執行),而Java的Task,或者說Java默認的線程建立工廠建立的線程是前臺線程(可配置) */ static async Task<int> FooAsync() { Thread.Sleep(2000); // 這一個會阻塞調用者 Console.WriteLine($"{nameof(FooAsync)}方法先同步耗時2秒"); // 注意,await以後的全部代碼是否阻塞調用者取決於調用者怎麼用這個方法,若是調用者是await FooAsync(),那麼這裏以後的全部代碼都是同步的 // TODO 因此async和await和Task<TResult>相比多了一個相似開關同樣的東西,可是這個開關是編譯階段肯定的,有點像模板類用的時候必須給出具體的類型,而這裏是具體的用法; // TODO 編譯時若是調用者有task.Result之類的wait操做,那麼【編譯器】就將FooAsync整個當成一個同步方法,若是沒有相關的操做,編譯器就將這部分及其後面的代碼做爲異步方法處理【應該是經過閉包造成一個新的Task對象】 // TODO 這裏往下的代碼,若是編譯器發現調用方不須要同步,那麼編譯器底層是經過再開一個Task<int>將result和下面的Thread.Sleep(4000)等代碼用閉包再包一層返回這個task對象; var result = await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"{nameof(FooAsync)}方法耗時3秒執行完畢,threadId:{Thread.CurrentThread.ManagedThreadId}"); return 8; }); // TODO 注意,這一塊是在上面的result執行完後才執行的,即上面先輸出 FooAsync方法耗時3秒執行完畢 而後等待4秒後再輸出 ###FooAsync方法先同步耗時4秒 Thread.Sleep(4000); // 重要,這一塊不必定會阻塞調用方(await放在Task.Run那裏),這一塊是否阻塞調用者看調用者是否須要對返回的Task對象進行wait,且是編譯時決定的; Console.WriteLine($"###{nameof(FooAsync)}方法先同步耗時4秒,threadIdNNN:{Thread.CurrentThread.ManagedThreadId}"); return result; }
通過測試,flagN行不管是否註釋,主線程的threadId和後面Task.Run及後面的threadIdNNN的線程id都是不一樣的,可是後面兩個的線程id是一致的,所以編譯器底層不是生成了FooAsync`1和FooAsync`2兩個方法,而是將await及後面的代碼合併爲
一塊,即FooAsync其實最終是變成這樣子的代碼(不必定百分百就是這個樣子,可是能夠解釋測試結果):
static async Task<int> FooAsync() { Thread.Sleep(2000); // 這一個會阻塞調用者 Console.WriteLine($"{nameof(FooAsync)}方法先同步耗時2秒"); // 注意,await以後的全部代碼是否阻塞調用者取決於調用者怎麼用這個方法,若是調用者是await FooAsync(),那麼這裏以後的全部代碼都是同步的 // TODO 因此async和await和Task<TResult>相比多了一個相似開關同樣的東西,可是這個開關是編譯階段肯定的,有點像模板類用的時候必須給出具體的類型,而這裏是具體的用法; // TODO 編譯時若是調用者有task.Result之類的wait操做,那麼【編譯器】就將FooAsync整個當成一個同步方法,若是沒有相關的操做,編譯器就將這部分及其後面的代碼做爲異步方法處理【應該是經過閉包造成一個新的Task對象】 // TODO 這裏往下的代碼,若是編譯器發現調用方不須要同步,那麼編譯器底層是經過再開一個Task<int>將result和下面的Thread.Sleep(4000)等代碼用閉包再包一層返回這個task對象; var result = Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"{nameof(FooAsync)}方法耗時3秒執行完畢,threadId:{Thread.CurrentThread.ManagedThreadId}"); var tmp = 8; // TODO 注意,這一塊是在上面的result執行完後才執行的,即上面先輸出 FooAsync方法耗時3秒執行完畢 而後等待4秒後再輸出 ###FooAsync方法先同步耗時4秒 Thread.Sleep(4000); // 重要,這一塊不必定會阻塞調用方(await放在Task.Run那裏),這一塊是否阻塞調用者看調用者是否須要對返回的Task對象進行wait,且是編譯時決定的; Console.WriteLine($"###{nameof(FooAsync)}方法先同步耗時4秒,threadIdNNN:{Thread.CurrentThread.ManagedThreadId}"); return 8; }); return result; }
這個result不會阻塞調用者;