1. 程式人生 > >C# 非同步程式設計async/await詳解

C# 非同步程式設計async/await詳解

非同步程式設計async/await詳解

1.關鍵字async

當函式使用async標記後,返回值必須為void,Task,Task<T>,當返回值為Task<T>時,函式內部只需要返回T型別,編譯器會自動包裝成Task<T>型別,如下兩個函式執行結果一致:

        public Task<int> F1()

        {

            return Task.FromResult(5);

        }



        public async Task<int> F2()

        {

            return 5;

        }

2.關鍵字await

await關鍵字必須在具有async標記的函式內使用,使用await關鍵字後編譯器會使用狀態機進行編譯,await關鍵字不僅僅只可以對Tsak物件使用,我們也可以自己實現await的物件。當函式使用了一個await關鍵字後,會被分割成兩部分,編譯的時候會當作函式的兩個狀態。函式的兩部分會分別是await前面的一部分,和await後面的一部分,例如:

這兩部分執行的位置不一樣,第一部分和正常函式一樣呼叫函式的時候直接執行,第二部分的執行取決於我們await的物件,執行的位置也在await物件內部。下面是一個await物件的實現,展示了各個部分的執行過程。

    public class A
    {
        private B b = new B();
        public B GetAwaiter()
        {
            Console.WriteLine("A.GetAwaiter");
            return b;
        }
    }

    public class B : INotifyCompletion
    {
        private bool isCompleted = false;
        public bool IsCompleted
        {
            get
            {
                Console.WriteLine($"Get IsCompleted={isCompleted}");
                return isCompleted;
            }
        }

        public void OnCompleted(Action continuation)
        {
            Console.WriteLine("B.OnCompleted");
            continuation();//await的第二部分會封裝成一個Action在此執行
            isCompleted = true;
        }

        public int GetResult()
        {
            Console.WriteLine("B.GetResult");
            return 4;
        }
    }

    class Program
    {
        static  A CreateAwaitObject()
        {
            Console.WriteLine("A.CreateAwaitObject");
            var t = new A();
            return t;
        } 

        static async void TestAwait(A a)
        {

            Console.WriteLine("Begin Test");
            var result = await a;
            Console.WriteLine($"End Test, Resutl:{result}");
        }

        static void Main(string[] args)
        {
            var a = CreateAwaitObject();
            TestAwait(a);//B.IsCompleted = false;
            Console.WriteLine("\n");
            TestAwait(a);//B.IsCompleted = true;
            Console.ReadKey();
        }
    }

執行結果:

如果一個物件可以await,這個物件必須實現GetAwaiter函式,可以是成員方法,也可是擴充套件方法。GetAwaite需要返回一個物件,返回的物件需要滿足3個要求:1、實現INotifyCompletion介面;2、實現GetResult函式;3、實現bool IsCompleted,get屬性。

從上面例子的執行結果可以很容易看出各個部分的執行順序,程式執行到await時首先會呼叫物件的GetAwaiter函式函式,然後呼叫GetAwaiter返回物件的IsCompleted屬性,當IsCompleted=falase時,會呼叫GetAwaiter返回物件的OnCompleted函式,OnCompleted函式有一個Action引數,這個引數也await將函式分割後第二部分的程式碼;當IsCompleted=true時,會直接呼叫await將函式封裝後第二部分的程式碼。

3.await與Task

await物件執行順序與物件的IsCompleted屬性有關,在await Task物件時,執行順序也與Task是否已經完成有關。當Task. IsCompleted=false時,這個時候會呼叫TaskAwaiter.OnCompleted函式,OnCompleted內部會呼叫Task.ContinueWith函式,傳入OnCompleted的Action引數。這樣當Task執行完成後就會繼續執行函式中await Task的第二部分的程式碼。當Task. IsCompleted=true時,程式會直接繼續執行await Task的第二部分的程式碼,和同步執行一樣。

如下:

    class Program
    {
        public static async Task<int> AwaitTask1()
        {
            var t = Task.FromResult(5);           
            var r = await t;
            Console.WriteLine("Task {0}", Thread.CurrentThread.ManagedThreadId);
            return r;

        }

        public static async Task<int> AwaitTask2()
        {
            Task<int> t = Task.Run(() =>
            {
                Console.WriteLine("Task {0}", Thread.CurrentThread.ManagedThreadId);
                Task.Delay(2000).Wait();
                return 5;
            });
            var r = await t;
            Console.WriteLine("Task {0}", Thread.CurrentThread.ManagedThreadId);
            return r;

        }

        static void Main(string[] args)
        {
            Console.WriteLine("Main1 {0}", Thread.CurrentThread.ManagedThreadId);
            Thread t = new Thread(() => {
                Console.WriteLine("Thread {0}", Thread.CurrentThread.ManagedThreadId);
                Console.WriteLine("\n");
                AwaitTask1().Wait();
                Console.WriteLine("\n");
                AwaitTask2().Wait();
            });
            t.Start();
            Console.Read();
        }
    }

結果: