async/await的特殊的地方

一: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不會阻塞調用者;

相關文章
相關標籤/搜索