async與await 淺談

簡介

.net4.5(C#5.0)之後,兩個異步語法糖——讓異步邏輯成爲了近似線性的流程。




基本語法

  1.  關鍵字:方法頭使用 async 修飾。

  2.  要求:包含 N(N>0) 個 await 表達式(不存在 await 表達式的話 IDE 會發出警告),表示需要異步執行的任務。

  3.  返回類型:只能返回 3 種類型(void、Task 和Task<T>)。Task 和Task<T> 標識返回的對象會在將來完成工作,表示調用方法和異步方法可以繼續執行。

  4.  參數:數量不限,但不能使用 out 和 ref 關鍵字。

  5.  命名約定:方法後綴名應以 Async 結尾。

  6.  其它:匿名方法和 Lambda 表達式也可以作爲異步對象;async 是一個上下文關鍵字;關鍵字 async 必須在返回類型前。



控制流


解釋:
  1. 事件處理程序調用並等待 AccessTheWebAsync() 異步方法。

  2. AccessTheWebAsync創建 HttpClient 對象並調用它的 GetStringAsync 異步方法來下載網站內容。

  3. 假設 GetStringAsync 中發生了某種情況,該情況掛起了它的進程。可能必須等待網站下載或一些其他阻塞的活動。爲避免阻塞資源,GetStringAsync() 會將控制權出讓給其調用方 AccessTheWebAsync。GetStringAsync 返回 Task,其中 TResult 爲字符串,並且 AccessTheWebAsync 將任務分配給 getStringTask 變量在工作完成時接收實際字符串值。

  4. 由於尚未等待 getStringTask,因此,AccessTheWebAsync 可以繼續執行不依賴於 GetStringAsync 得出最終結果的其他任務。該任務由對同步方法 DoIndependentWork 的調用表示。

  5. DoIndependentWork 是完成其工作並返回其調用方的同步方法。

  6. AccessTheWebAsync 已完成工作,可以不受 getStringTask 的結果影響。 接下來,AccessTheWebAsync 需要計算並返回該下載字符串的長度,但該方法僅在具有字符串時才能計算該值。因此,AccessTheWebAsync 使用一個 await 運算符來掛起其進度,並把控制權交給調用 AccessTheWebAsync 的方法。AccessTheWebAsync 將 Task<int> 返回至調用方。 該任務產生下載字符串長度返回。如果 GetStringAsync(即 getStringTask)在 AccessTheWebAsync 等待前完成,則控制權會保留在 AccessTheWebAsync 中。 如果異步調用過程 (getStringTask) 已完成,並且 AccessTheWebSync 不必等待最終結果,則掛起然後返回到 AccessTheWebAsync,但這會造成成本的浪費。

  7. GetStringAsync 完成並生成一個字符串結果。 字符串結果不是通過你預期的方式調用 GetStringAsync 所返回的。(此方法已在步驟 3 中返回一個任務。)相反,字符串結果存儲在表示完成方法 getStringTask 的任務中。 await 運算符從 getStringTask 中檢索結果。賦值語句將檢索到的結果賦給 urlContents

     AccessTheWebAsync 具有字符串結果時,該方法可以計算字符串長度。然後,AccessTheWebAsync 工作也將完成,並且等待事件處理程序可繼續使用。


編譯流

 以一個簡單的指控臺程序爲例

static void Main(string[] args)
{
    new Program().SampleMethodAsync();
    Console.WriteLine("THERE");
Console.Read();}async void SampleMethodAsync() {
    Console.WriteLine(await GetHere());
}

 Task<string> GetHere()
{
    return Task.Run(() =>
    {
        Thread.Sleep(1000);
        return "HERE";
    });
}

從ILSPY中我們可以看出async方法中的代碼會被編譯至

// System.Runtime.CompilerServices.AsyncMethodBuilderCore
[DebuggerStepThrough, SecuritySafeCritical]
internal void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
    if (stateMachine == null)
    {
        throw new ArgumentNullException("stateMachine");
    }
    Thread currentThread = Thread.CurrentThread;
    ExecutionContextSwitcher executionContextSwitcher = default(ExecutionContextSwitcher);
    RuntimeHelpers.PrepareConstrainedRegions();
    try
    {
        ExecutionContext.EstablishCopyOnWriteScope(currentThread, false, ref executionContextSwitcher);
        stateMachine.MoveNext();
    }
    finally
    {
        executionContextSwitcher.Undo(currentThread);
    }
}

注:stateMachine就是SampleMethod方法編譯時生成的內部結構體


再看MoveNext的具體實現

bool '<>t__doFinallyBodies';
Exception '<>t__ex';
int CS$0$0000;
TaskAwaiter<string> CS$0$0001;
TaskAwaiter<string> CS$0$0002;

try
{
    '<>t__doFinallyBodies' = true;
    CS$0$0000 = this.'<>1__state';
    if (CS$0$0000 != 0)
    {
        CS$0$0001 = this.'<>4__this'.GetHere().GetAwaiter();
        if (!CS$0$0001.IsCompleted)
        {
            this.'<>1__state' = 0;
            this.'<>u__$awaiter1' = CS$0$0001;
            this.'<>t__builder'.AwaitUnsafeOnCompleted(ref CS$0$0001, ref this);
            '<>t__doFinallyBodies' = false;
            return;
        }
    }
    else
    {
        CS$0$0001 = this.'<>u__$awaiter1';
        this.'<>u__$awaiter1' = CS$0$0002;
        this.'<>1__state' = -1;
    }

    Console.WriteLine(CS$0$0001.GetResult());
}

  1. GetHere()返回Task<string>
  2. GetAwaiter()返回TaskAwaiter<string>

注:TaskAwaiter的屬性,可以看出我們可以對異步方法進一步擴展

public struct TaskAwaiter : ICriticalNotifyCompletion, INotifyCompletion
    {

        public bool IsCompleted { get; }
        public void GetResult();
        public void OnCompleted(Action continuation);
        public void UnsafeOnCompleted(Action continuation);
    }

// System.Runtime.CompilerServices.AsyncVoidMethodBuilder
[__DynamicallyInvokable, SecuritySafeCritical]
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
    ref TAwaiter awaiter, ref TStateMachine stateMachine)
    where TAwaiter : ICriticalNotifyCompletion
    where TStateMachine : IAsyncStateMachine
{
    try
    {
        Action completionAction = this.m_coreState
            .GetCompletionAction<AsyncVoidMethodBuilder, TStateMachine>(ref this, ref stateMachine);
        awaiter.UnsafeOnCompleted(completionAction);
    }
    catch (Exception exception)
    {
        AsyncMethodBuilderCore.ThrowAsync(exception, null);
    }
}
  1. 創建了一個Action,MoveNext方法的信息已經隨着stateMachine被封裝進去了。
  2. 把Action交給Awaiter,讓它在await的操作完成後執行這個Action。
    
// System.Runtime.CompilerServices.AsyncMethodBuilderCore
[SecuritySafeCritical]
internal Action GetCompletionAction<TMethodBuilder, TStateMachine>(ref TMethodBuilder builder, ref TStateMachine stateMachine)
    where TMethodBuilder : IAsyncMethodBuilder
    where TStateMachine : IAsyncStateMachine
{
    Debugger.NotifyOfCrossThreadDependency();
    ExecutionContext executionContext = ExecutionContext.FastCapture();
    Action action;
    AsyncMethodBuilderCore.MoveNextRunner moveNextRunner;
    if (executionContext != null && executionContext.IsPreAllocatedDefault)
    {
        action = this.m_defaultContextAction;
        if (action != null)
        {
            return action;
        }
        moveNextRunner = new AsyncMethodBuilderCore.MoveNextRunner(executionContext);
        action = new Action(moveNextRunner.Run);
        if (AsyncCausalityTracer.LoggingOn)
        {
            action = (this.m_defaultContextAction = this.OutputAsyncCausalityEvents<TMethodBuilder>(ref builder, action));
        }
        else
        {
            this.m_defaultContextAction = action;
        }
    }
    else
    {
        moveNextRunner = new AsyncMethodBuilderCore.MoveNextRunner(executionContext);
        action = new Action(moveNextRunner.Run);
        if (AsyncCausalityTracer.LoggingOn)
        {
            action = this.OutputAsyncCausalityEvents<TMethodBuilder>(ref builder, action);
        }
    }
    if (this.m_stateMachine == null)
    {
        builder.PreBoxInitialization<TStateMachine>(ref stateMachine);
        this.m_stateMachine = stateMachine;
        this.m_stateMachine.SetStateMachine(this.m_stateMachine);
    }
    moveNextRunner.m_stateMachine = this.m_stateMachine;
    return action;
}

  1. MoveNextRunner的Run方法被封裝到了返回的Action裏。
  2. Run方法裏面有兩條分支:
    1.直接調用MoveNext
    2.通過InvokeMoveNext調用MoveNext



執行action

// System.Threading.Tasks.Task
[SecurityCritical]
internal void SetContinuationForAwait(
    Action continuationAction,
    bool continueOnCapturedContext,
    bool flowExecutionContext,
    ref StackCrawlMark stackMark)
{
    TaskContinuation taskContinuation = null;
    if (continueOnCapturedContext)
    {
        SynchronizationContext currentNoFlow = SynchronizationContext.CurrentNoFlow;
        if (currentNoFlow != null && currentNoFlow.GetType() != typeof(SynchronizationContext))
        {
            taskContinuation = new SynchronizationContextAwaitTaskContinuation(
                currentNoFlow, continuationAction, flowExecutionContext, ref stackMark);
        }
        else
        {
            TaskScheduler internalCurrent = TaskScheduler.InternalCurrent;
            if (internalCurrent != null && internalCurrent != TaskScheduler.Default)
            {
                taskContinuation = new TaskSchedulerAwaitTaskContinuation(
                    internalCurrent, continuationAction, flowExecutionContext, ref stackMark);
            }
        }
    }
    if (taskContinuation == null && flowExecutionContext)
    {
        taskContinuation = new AwaitTaskContinuation(continuationAction, true, ref stackMark);
    }
    if (taskContinuation != null)
    {
        if (!this.AddTaskContinuation(taskContinuation, false))
        {
            taskContinuation.Run(this, false);
            return;
        }
    }
    else if (!this.AddTaskContinuation(continuationAction, false))
    {
        AwaitTaskContinuation.UnsafeScheduleAction(continuationAction, this);
    }
}
// System.Threading.Tasks.AwaitTaskContinuation
[SecurityCritical]
internal static void UnsafeScheduleAction(Action action, Task task)
{
    AwaitTaskContinuation awaitTaskContinuation = new AwaitTaskContinuation(action, false);
    TplEtwProvider log = TplEtwProvider.Log;
    if (log.IsEnabled() && task != null)
    {
        awaitTaskContinuation.m_continuationId = Task.NewId();
        log.AwaitTaskContinuationScheduled(
            (task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id,
            task.Id,
            awaitTaskContinuation.m_continuationId);
    }
    ThreadPool.UnsafeQueueCustomWorkItem(awaitTaskContinuation, false);
}

// System.Threading.ThreadPool
[SecurityCritical]
internal static void UnsafeQueueCustomWorkItem(IThreadPoolWorkItem workItem, bool forceGlobal)
{
    ThreadPool.EnsureVMInitialized();
    try
    {
    }
    finally
    {
        ThreadPoolGlobals.workQueue.Enqueue(workItem, forceGlobal);
    }
}

QueueUserWorkItem方法內部調用了ThreadPoolGlobals.workQueue.Enqueue——異步的方法就是在這裏被執行。




再來看一下一種情況

async void SampleMethod()
{
    Console.WriteLine("WHERE");
    Console.WriteLine(await GetHere());
}


此時的MoveNext

try
{
    '<>t__doFinallyBodies' = true;
    CS$0$0000 = this.'<>1__state';
    if (CS$0$0000 != 0)
    {
        Console.WriteLine("WHERE");
        CS$0$0001 = this.'<>4__this'.GetHere().GetAwaiter();
        if (!CS$0$0001.IsCompleted)
        {
            this.'<>1__state' = 0;
            this.'<>u__$awaiter1' = CS$0$0001;
            this.'<>t__builder'.AwaitUnsafeOnCompleted(ref CS$0$0001, ref this);
            '<>t__doFinallyBodies' = false;
            return;
        }
    }
    else
    {
        CS$0$0001 = this.'<>u__$awaiter1';
        this.'<>u__$awaiter1' = CS$0$0002;
        this.'<>1__state' = -1;
    }

    Console.WriteLine(CS$0$0001.GetResult());
}

可以看出來await前後的代碼被放到了兩個區塊裏,而這兩個區塊,也就是MoveNext的兩個分支





總結

  1. async方法中的代碼會被移交給IAsyncStateMachine(狀態機)的MoveNext方法。
  2. async方法中await操作前後的代碼被分離,無論方法是同步還是異步都可以用async關鍵字來進行標識,因爲用async標識只是顯示錶明在該方法內可能會用到await關鍵字使其變爲異步方法,而且將該異步方法進行了明確的劃分,只有用了await關鍵字時纔是異步操作,其餘一併爲同步操作

  3. 主線程直接執行await前的代碼(同步),並將await的Task移交給線程池ThreadPoolGlobal(異步)。
  4. 子線程執行完主線程遞交來的Task後,再次走入MoveNext方法,執行await後的代碼。(同步或異步)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章