非同步程式設計之async&await
async&await定義
首先async&await是語法糖。是C#5.0後支援的一種非同步程式設計快捷方式。async書寫在方法上,表示該方法是一個非同步方法,同時,async與await一定是配套使用的,async非同步方法的返回型別僅有三種: void,Task,Task<T>方法內部使用await關鍵字標明開始執行非同步程式碼。await運算子的運算元通常是以下其中一個 .NET 型別:
Task、Task<TResult>、ValueTask 或 ValueTask<TResult>
但是,任何可等待表示式都可以是await
運算子的運算元。 有關詳細資訊,請參閱
非同步方法寫法如下:
public async void WithOutReturn() { await Task.Factory.StartNew(()=>{ Console.WriteLine("task is running...."); }); await foreach... //C#8.0開始支援迭代非同步流 await using... //C#8.0使用 await using 語句來處理非同步可釋放物件}
執行順序
主執行緒執行async非同步方法時,會同步執行await標誌前的程式碼,當遇到await語句時,主執行緒會返回到呼叫async語句處執行後續語句,await標誌的方法或者語句為非同步執行,其後的程式碼相當於"回撥函式",在await執行完後繼續執行。
舉例說明:
同步例子:
private async static Task Test() { Console.WriteLine($"當前主執行緒id={Thread.CurrentThread.ManagedThreadId.ToString("00"View Code)}"); { NoReturnNoAwait(); } Console.WriteLine($"Main Thread Task ManagedThreadId= {Thread.CurrentThread.ManagedThreadId}"); Console.Read(); } private static void NoReturnNoAwait() { //主執行緒執行 Task task = Task.Run(() =>//啟動新執行緒完成任務 { Console.WriteLine($"NoReturnNoAwait Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); Console.WriteLine($"NoReturnNoAwait Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}"); }); //主執行緒執行 Console.WriteLine($"NoReturnNoAwait Sleep after Task,ThreadId={Thread.CurrentThread.ManagedThreadId}"); }
async非同步方法不帶返回值
非同步例子:
private async static Task Test() { Console.WriteLine($"當前主執行緒id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); { NoReturn(); } Console.Read(); }
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 Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(3000); Console.WriteLine($"NoReturn Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}"); }); await task; //主執行緒到await這裡就返回了,執行主執行緒任務 //同時task的子執行緒就開始工作,直到Task完成,然後繼續後續任務(後續任務的執行緒ID不一定是這個子執行緒,可以是子執行緒,也可以是其他執行緒,還可以是主執行緒) //像什麼? 效果上等價於continuewith //task.ContinueWith(t => //{ // Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}"); //}); Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}"); }View Code
await標誌前的程式碼是同步執行,await標誌的方法是非同步執行,await標誌的方法後面的程式碼相當於"回撥函式",在await標誌的非同步方法後面執行。
async非同步方法帶返回值Task
private async static Task Test() { Console.WriteLine($"當前主執行緒id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); { Task t = NoReturnTask(); Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); } Console.Read(); }
private static async Task NoReturnTask() //在async/await方法裡面如果沒有返回值,預設返回一個Task { //這裡還是主執行緒的id Console.WriteLine($"NoReturnTask Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}"); Task task = Task.Run(() => { Console.WriteLine($"NoReturnTask Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); Console.WriteLine($"NoReturnTask Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}"); }); await task; Console.WriteLine($"NoReturnTask Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}"); //return; //return new TaskFactory().StartNew(() => { }); //不能return 沒有async才行 }
當此處加上return語句後,編譯器提示:
嚴重性 程式碼 說明 專案 檔案 行 禁止顯示狀態
錯誤 CS1997 由於“AwaitAsyncClass.NoReturnTask()”是返回“Task”的非同步方法,因此返回關鍵字不能後接物件表示式。是否要返回“Task<T>”?
在使用async/await非同步方法時,如果方法裡面如果沒有返回值,預設返回一個Task,其表示該非同步方法無返回值。
無返回值的情況下,async Task 等同於async void,兩者間的區別是:非同步函式簽名為async Task(Task<T>)時,函式呼叫處可以使用await, Task.WhenAny, Task.WhenAll等方式組合使用;而Async Void 則不行。
接收非同步方法的task一般可以這樣處理:
1、阻塞式
private async static Task Test() { Console.WriteLine($"當前主執行緒id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); { Task t = NoReturnTask(); t.Wait();//主執行緒等待Task的完成 阻塞的【呼叫Task.Wait()
Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
}
Console.Read();
}
2、非阻塞
private async static Task Test() { Console.WriteLine($"當前主執行緒id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); { Task t = NoReturnTask(); await t;//await後的程式碼會由執行緒池的執行緒執行 非阻塞 Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); } Console.Read(); }
結果與上面的一致。是因為當主執行緒執行Test()方法中await t後,返回Test()呼叫處繼續往下執行。Test()方法中Task t = NoReturnTask()在await之前,所以會先執行完畢(雖然開啟的是一個子執行緒),此時test裡面的
Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
等同於
t.ContinueWith(t =>
{Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");});
再增加幾條列印,修改子執行緒休眠順序,結果更加清晰。
public static void TestShow() { Test(); Console.WriteLine($"TestShow Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine("主執行緒執行Test後,遇await返回,往下執行"); }
private async static Task Test() { Console.WriteLine($"當前主執行緒id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); { Task t = NoReturnTask(); await t;//await後的程式碼會由執行緒池的執行緒執行 非阻塞 Console.WriteLine($"Thread Task in Test method ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); } Console.Read(); }
private static async Task NoReturnTask() //在async/await方法裡面如果沒有返回值,預設返回一個Task { //這裡還是主執行緒的id Console.WriteLine($"NoReturnTask Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}"); Task task = Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"NoReturnTask Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"NoReturnTask Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}"); }); await task; Console.WriteLine($"NoReturnTask Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}"); //return; // return new TaskFactory().StartNew(() => { }); //不能return 沒有async才行 }View Code
async非同步方法帶返回值Task<T>
private async static Task Test() { Console.WriteLine($"當前主執行緒id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Task<long> t = SumAsync(); Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); long lResult = t.Result;//訪問result,阻塞式 主執行緒等待所有的任務挖成 //如果訪問Result,就相當於是同步方法! //t.Wait();//等價於上一行,阻塞式--同步 //await t;//非阻塞, }
/// <summary> /// 帶返回值的Task /// 要使用返回值就一定要等子執行緒計算完畢 /// </summary> /// <returns>async 就只返回long</returns> private static async Task<long> SumAsync() { Console.WriteLine($"SumAsync 111 start ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); long result = 0; int sum = 5; await Task.Run(() => { for (int k = 0; k < sum; k++) { Console.WriteLine($"SumAsync {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); } for (long i = 0; i < 999_999_999; i++) { result += i; } }); Console.WriteLine($"SumFactory 111 end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); await Task.Run(() => { for (int k = 0; k < sum; 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}"); await Task.Run(() => { for (int k = 0; k < sum; 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; }View Code
原理解析
static void Main(string[] args) { Test(); Console.WriteLine($"main thread id={Thread.CurrentThread.ManagedThreadId}"); Console.ReadKey(); } static async void Test() { await Task.Factory.StartNew(() => { Task.Delay(1000);//當前任務延時1s後執行 Console.WriteLine($"execute proc,thread id={Thread.CurrentThread.ManagedThreadId}"); }); Console.WriteLine($"after await,thread id={Thread.CurrentThread.ManagedThreadId} "); }View Code
通過ILSpy檢視其C#程式碼如下:
注意:ILSpy 選項->反編譯器項要去掉C#5.0(反編譯非同步方法條目)
private static void Main(string[] args) { Test(); Console.WriteLine($"main thread id={Thread.CurrentThread.ManagedThreadId}"); Console.ReadKey(); } [AsyncStateMachine(typeof(<Test>d__1))] [DebuggerStepThrough] private static void Test() { <Test>d__1 stateMachine = new <Test>d__1(); stateMachine.<>t__builder = AsyncVoidMethodBuilder.Create(); stateMachine.<>1__state = -1; stateMachine.<>t__builder.Start(ref stateMachine); }
Main方法沒有變化,Test方法與原來的不同,看來編譯器幫我們做了許多事情來通過async,await來實現非同步方法。
這裡Test方法主要做了這幾件事情:
1.例項化 <Test>d__1類物件 stateMachine(狀態機),並對 t__builder和1__state(狀態)賦初值
2.執行 stateMachine.<>t__builder.Start(ref stateMachine);【實際是呼叫 AsyncVoidMethodBuilder結構體下的 Start方法】
我們首先看 <Test>d__1類
[CompilerGenerated] private sealed class <Test>d__1 : IAsyncStateMachine { public int <>1__state; public AsyncVoidMethodBuilder <>t__builder;//此處Test同步方法時一個void函式,其對應的method builder為AsyncVoidMethodBuilder
private TaskAwaiter <>u__1; private void MoveNext() { int num = <>1__state; try { TaskAwaiter awaiter; if (num != 0) { awaiter = Task.Factory.StartNew(delegate { Task.Delay(1000); Console.WriteLine($"execute proc,thread id={Thread.CurrentThread.ManagedThreadId}"); }).GetAwaiter(); if (!awaiter.IsCompleted) { num = (<>1__state = 0); <>u__1 = awaiter; <Test>d__1 stateMachine = this; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); return; } } else { awaiter = <>u__1; <>u__1 = default(TaskAwaiter); num = (<>1__state = -1); } awaiter.GetResult(); Console.WriteLine($"after await,thread id={Thread.CurrentThread.ManagedThreadId} "); } catch (Exception exception) { <>1__state = -2; <>t__builder.SetException(exception); return; } <>1__state = -2; <>t__builder.SetResult();
AsyncVoidMethodBuilder結構體下的 Start方法
public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{ if (stateMachine == null) { throw new ArgumentNullException("stateMachine"); } ExecutionContextSwitcher ecsw = default(ExecutionContextSwitcher); RuntimeHelpers.PrepareConstrainedRegions(); try { ExecutionContext.EstablishCopyOnWriteScope(ref ecsw); stateMachine.MoveNext();// } finally { ecsw.Undo(); } }
此處泛型約束stateMachine變數是實現IAsyncStateMachine介面的物件,而實際傳入的ref stateMachine為實現了
IAsyncStateMachine介面的<Test>d__1密封類,故stateMachine.MoveNext()實際呼叫的是<Test>d__1下的MoveNext()方法:
說明:此處如果awaiter
// System.Runtime.CompilerServices.AsyncVoidMethodBuilder using System.Security; using System.Threading.Tasks; [SecuritySafeCritical] [__DynamicallyInvokable] public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine { try { AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null; Action completionAction = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? Task : null, ref runnerToInitialize);//建立action回撥函式 if (m_coreState.m_stateMachine == null) { if (AsyncCausalityTracer.LoggingOn) { AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, Task.Id, "Async: " + stateMachine.GetType().Name, 0uL); } m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, null); } awaiter.UnsafeOnCompleted(completionAction);//Action交給Awaiter,讓它在await的操作完成後執行這個Action,後續可以看到這個action委託繫結的函式實際為run,即awaiter操作完畢->action動作執行->run方法開始觸發執行 } catch (Exception exception) { AsyncMethodBuilderCore.ThrowAsync(exception, null); } }
此處主要完成兩件事:
一是建立了一個Action,MoveNext方法的資訊已經隨著stateMachine被封裝進去了。
二是把上面這個Action交給Awaiter,讓它在awaiter的操作完成後執行這個Action。
而moveNextRunner.Run方法實際所做的事情就是執行此時狀態機的MoveNext方法。
即執行stateMachine.MoveNext()
促使狀態機繼續進行狀態流轉,迭代。
【ExecutionContext上下文後續研究】
[SecuritySafeCritical] internal void Run() { if (m_context != null) { try { ContextCallback callback = InvokeMoveNext; ExecutionContext.Run(m_context, callback, m_stateMachine, preserveSyncCtx: true); } finally { m_context.Dispose(); } } else { m_stateMachine.MoveNext(); } }
[SecurityCritical] private static void InvokeMoveNext(object stateMachine) { ((IAsyncStateMachine)stateMachine).MoveNext(); }