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();
}
}
結果: