1. 程式人生 > >20181110_wait和async

20181110_wait和async

一. Awit和async的由來:

await/async本身是一個語法糖,編譯器提供的一個簡化程式設計的功能; 在C#升級和.net Framework升級的時候, 產生的, 所以說並不是CLR的產物

二. 用法:

a)   Async出現在方法的宣告上, 任何一個方法新增一個async關鍵字都不會報錯

b)   如果只有awit, 是會報錯的

c)   Awit必須放在task前面, 必須和async成對出現

d)   Awit和async成對出現, 會被編譯成狀態機

三. 一個簡單的示例:

private static async void NoReturn()
        {
            //主執行緒執行
            Console.WriteLine($"NoReturn Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
            TaskFactory taskFactory = new TaskFactory();
            //主執行緒啟動一個子執行緒執行
            Task task = taskFactory.StartNew(() =>
            {
                Console.WriteLine($"NoReturn Sleep before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
                Thread.Sleep(3000);
                Console.WriteLine($"NoReturn Sleep after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
            });
            await task;//主執行緒碰到await就返回去執行主執行緒的其它任務(迴圈), 而這個方法的下面的程式碼則不再執行, 等待主執行緒把其它任務執行完畢, 程式會再次跳回來執行這個方法的下面的其它程式碼; 但是注意, 再次跳回來的時候, 並不一定是主執行緒執行, 也有可能是新開一個執行緒來執行
            //這個回撥的執行緒是不確定的:可能是主執行緒  可能是子執行緒  也可能是其他執行緒
            Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
        }


	//呼叫程式碼:


		NoReturn();
                for (int i = 0; i < 10; i++)
                {
                    Thread.Sleep(300);
                    Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId} i={i}");
    
                }

四. Await和async的返回值

a) 當使用await和async時, 如果沒有返回值, 則應該標明使用Task作為返回值, 下面的程式碼演示沒有返回值的await和async:

private static async Task  NoReturnTask()
        {
            //這裡還是主執行緒的id
            Console.WriteLine($"NoReturnTask Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}");

            Task task = Task.Run(() =>
             {
                 Console.WriteLine($"NoReturnTask Sleep before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
                 Thread.Sleep(3000);
                 Console.WriteLine($"NoReturnTask Sleep after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
             });
            await task;
            Console.WriteLine($"NoReturnTask Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
             
        }

 b)  當使用await和async時, 如果有返回值, 則應該和Task組成泛型來返回: Task<typeName>, 下面的程式碼演示有返回值的await和async

private static async Task<long> SumAsync()
        { 
            Console.WriteLine($"SumAsync 111 start ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
            long result = 0;
            //1. 先啟動一個執行緒, 主執行緒遇到這裡之後, 就會返回去了, 這個子執行緒開始執行
            await Task.Run(() =>
            { 
                for (long i = 0; i < 999999999; i++)
                {
                    result += i;
                }
            }); 
            return result;
        }

c)  Awati和async返回值的使用:

Task<long> t = SumAsync();
                Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                long lResult = t.Result;//當需要訪問result時, 則當前執行緒必須等待t執行緒的完成
                t.Wait();//等價於上一行

五.  利用await和async像寫同步程式碼一樣編寫非同步執行的程式碼, 呼叫方法和第四步的c相同:

/// <summary>
        /// 帶返回值的Task  
        /// 要使用返回值就一定要等子執行緒計算完畢
        /// </summary>
        /// <returns>async 就只返回long</returns>
        private static async Task<long> SumAsync()
        {
            //在await 和 async 中, 先啟動一個執行緒, 執行完成之後, 接著可以繼續awit
            //有點類似同步的方式程式設計, 但是卻是非同步執行

           
            Console.WriteLine($"SumAsync 111 start ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
            long result = 0;
            //1. 先啟動一個執行緒, 主執行緒遇到這裡之後, 就會返回去了, 這個子執行緒開始執行
            await Task.Run(() =>
            {
                for (int k = 0; k < 10; k++)
                {
                    Console.WriteLine($"SumAsync {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                    Thread.Sleep(1000);
                }

                for (long i = 0; i < 999999999; i++)
                {
                    result += i;
                }
            });
            //2. 上面的執行緒執行完成之後, 輸出下面這句話; 可以看做這裡是其它的動作
            Console.WriteLine($"SumFactory 111   end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");

            //3. 然後繼續執行下面這裡的執行緒, 這裡又有一個await, 然後主執行緒又返回去執行其他的
            await Task.Run(() =>
            {
                for (int k = 0; k < 10; k++)
                {
                    Console.WriteLine($"SumAsync {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                    Thread.Sleep(1000);
                }

                for (long i = 0; i < 999999999; i++)
                {
                    result += i;
                }
            });
            //4. 這個執行緒執行完成之後, 開始執行這裡輸出
            Console.WriteLine($"SumFactory 111   end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");

            //5. 碰到await之後, 開了一個子執行緒, 然後又返回去了
            await Task.Run(() =>
            {
                for (int k = 0; k < 10; k++)
                {
                    Console.WriteLine($"SumAsync {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                    Thread.Sleep(1000);
                }

                for (long i = 0; i < 999999999; i++)
                {
                    result += i;
                }
            });

            Console.WriteLine($"SumFactory 111   end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
            
            return result;
        } 

六. 總結:

    // 1 使用async和await , 能夠像寫同步程式碼的方式一樣, 達到非同步執行的功能

      // 2 如果 awit / async 沒有返回值, 則使用Task來替代(雖然允許寫成void, 但是不要那樣做);無返回值時  async Task == async void

      // 3 如果有返回值則使用Task<型別> 來處理

     // 4 await只能出現在Task前面

     // 5 不能單獨await

     // 6 await 只能放在task前面

    // 7 不管你的await和async標識的方法需不需要返回值, 都不推薦void返回, 應該使用Task來代替, 像第二條所說那樣, 因為返回Task和Task<T>則能夠使用await, 並且可以和Task.WhenAny, Task.WhenAll等方式組合使用, 但是如果返回Void 不行, 則這條執行緒鏈就斷了

    // 8 如果 awit / async 沒有返回值, 則使用Task來替代, 如果有返回值則使用Task<型別>來處理