1. 程式人生 > 其它 >非同步程式設計之async&await

非同步程式設計之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運算子的運算元。 有關詳細資訊,請參閱

C# 語言規範中的可等待表示式部分。

非同步方法寫法如下:

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"
)}"); { 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}"); }
View Code

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