C# async/await異步編程深刻理解

異步函數簡介異步

通常指 async 修飾符聲明得、可包含await表達式得方法或匿名函數。async

 

聲明方式ide

異步方法的聲明語法與其餘方法徹底同樣, 只是須要包含 async 關鍵字。async能夠出如今返回值以前的任何位置, 以下示例:函數

 async public static void GetInfoAsync()
        {
           //...
        }

        public async static void GetInfoAsync()
        {
           //...
        }

        public static async void GetInfoAsync()
        {
            //...
        }

異步方法的返回類型工具

異步函數的返回類型只能爲: void、Task、Task<TResult>。ui

Task<TResult>: 表明一個返回值T類型的操做。this

Task: 表明一個無返回值的操做。spa

void: 爲了和傳統的事件處理程序兼容而設計。設計

 

await(等待)3d

await等待的是什麼? 能夠是一個異步操做(Task)、亦或者是具有返回值的異步操做(Task<TResult>)的值, 以下:

        public async static void GetInfoAsync()
        {
            await GetData(); // 等待異步操做, 無返回值
            await GetData<int>(1); //等待異步操做, 返回值 int
        }

        static Task GetData()
        {
            //...
            return null;
        }

        static Task<T> GetData<T>(int a)
        {
            //...
            return null;
        }

注: await 最終操做的是一個值, 固然, 也能夠是無值,  如上GetData() , 不然就是一個 Task<T>  如上:  GetData<T>()

await執行過程

TaskAwaiter 獲取執行結果

通常而言, await等待的一個異步操做, 不管是具有返回值仍是否, 那麼最終都會得到該操做是否已完成、具有返回值得異步操做能夠獲取他得返回結果。

因此這個時候, TaskAwaiter出現了, 不管是Task、仍是Task<TResult>操做, 都具有GetAwaiter() 方法。

用於獲取改操做得狀態、返回結果, 及部分操做, 以下TaskAwaiter結構: 

    //
    // 摘要:
    //     提供等待異步任務完成的對象。
    public struct TaskAwaiter : ICriticalNotifyCompletion, INotifyCompletion
    {
        //
        // 摘要:
        //     獲取一個值,該值指示是否已完成的異步任務。
        //
        // 返回結果:
        //     true 若是任務已完成;不然爲 false。
        //
        // 異常:
        //   T:System.NullReferenceException:
        //     System.Runtime.CompilerServices.TaskAwaiter 對象未正確初始化。
        public bool IsCompleted { get; }

        //
        // 摘要:
        //     結束異步任務完成以前的等待。
        //
        // 異常:
        //   T:System.NullReferenceException:
        //     System.Runtime.CompilerServices.TaskAwaiter 對象未正確初始化。
        //
        //   T:System.Threading.Tasks.TaskCanceledException:
        //     任務已取消。
        //
        //   T:System.Exception:
        //     在完成的任務 System.Threading.Tasks.TaskStatus.Faulted 狀態。
        [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
        public void GetResult();
        //
        // 摘要:
        //     設置時應執行的操做 System.Runtime.CompilerServices.TaskAwaiter 對象中止等待異步任務完成。
        //
        // 參數:
        //   continuation:
        //     要在等待操做完成時執行的操做。
        //
        // 異常:
        //   T:System.ArgumentNullException:
        //     continuation 爲 null。
        //
        //   T:System.NullReferenceException:
        //     System.Runtime.CompilerServices.TaskAwaiter 對象未正確初始化。
        [SecuritySafeCritical]
        public void OnCompleted(Action continuation);
        //
        // 摘要:
        //     計劃程序與此等待異步任務的延續任務操做。
        //
        // 參數:
        //   continuation:
        //     要等待操做完成時調用的操做。
        //
        // 異常:
        //   T:System.ArgumentNullException:
        //     continuation 爲 null。
        //
        //   T:System.InvalidOperationException:
        //     該等待程序未正確初始化。
        [SecurityCritical]
        public void UnsafeOnCompleted(Action continuation);
    }

 接下來, 演示如何經過等待去獲取異步操做的返回結果, 以下代碼所示:

        public async static void GetInfoAsync()
        {
            Task<bool> task = Task.Run<bool>(() =>
            {
                Thread.Sleep(10000); //模擬耗時
                return true;
            });
            
            //如下兩種方式
            bool taskResult1 = await task;  //內部本身執行了GetAwaiter() bool taskResult = task.GetAwaiter().GetResult();  //本身手動執行Awaiter(), 可是阻塞UI
       Console.WriteLine(taskResult);
}

  注: 對於一個await表達式, 編譯器生成的代碼會先調用GetAwaiter(), 而後經過awaiter得成員來等待結果, 因此以上兩種方式等效( 不考慮阻塞的狀況下)

爲了驗證以上猜想, 經過反編譯工具查看獲得以下代碼:

編譯器最終生成兩個密封類, 一個類( <>c )咱們展開分析:

<GetInfoAsync>b__1_0()  正是模擬耗時的一個操做委託生成的方法。 

        [CompilerGenerated]
        [Serializable]
        private sealed class <>c
        {
            public static readonly Program.<>c <>9 = new Program.<>c();
            public static Func<bool> <>9__1_0;
            internal bool <GetInfoAsync>b__1_0()
            {
                Thread.Sleep(10000);
                return true;
            }
        }

第二個類 <GetInfoAsync>d__1 分析:

該類分別實現了接口  IAsyncStateMachine 的MoveNext() 與 SetStateMachine() ,另外 注意,

還特別定義了一個 <>t__builder, 先記住他, 下面講會對他講到, 爲何編譯器生成的代碼會默認先調用GetAwaiter()

 1 [CompilerGenerated]
 2         private sealed class <GetInfoAsync>d__1 : IAsyncStateMachine
 3         {
 4             public int <>1__state;
 5             public AsyncVoidMethodBuilder <>t__builder;
 6             private Task<bool> <task>5__1;
 7             private bool <result>5__2;
 8             private bool <>s__3;
 9             private TaskAwaiter<bool> <>u__1;
10             void IAsyncStateMachine.MoveNext()
11             {
12                 int num = this.<>1__state;
13                 try
14                 {
15                     TaskAwaiter<bool> awaiter;
16                     if (num != 0)
17                     {
18                         Func<bool> arg_2F_0;
19                         if ((arg_2F_0 = Program.<>c.<>9__1_0) == null)
20                         {
21                             arg_2F_0 = (Program.<>c.<>9__1_0 = new Func<bool>(Program.<>c.<>9.<GetInfoAsync>b__1_0));
22                         }
23                         this.<task>5__1 = Task.Run<bool>(arg_2F_0);
24                         awaiter = this.<task>5__1.GetAwaiter();
25                         if (!awaiter.IsCompleted)
26                         {
27                             this.<>1__state = 0;
28                             this.<>u__1 = awaiter;
29                             Program.<GetInfoAsync>d__1 <GetInfoAsync>d__ = this;
30                             this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<bool>, Program.<GetInfoAsync>d__1>(ref awaiter, ref <GetInfoAsync>d__);
31                             return;
32                         }
33                     }
34                     else
35                     {
36                         awaiter = this.<>u__1;
37                         this.<>u__1 = default(TaskAwaiter<bool>);
38                         this.<>1__state = -1;
39                     }
40                     this.<>s__3 = awaiter.GetResult();
41                     this.<result>5__2 = this.<>s__3;
42                     Console.WriteLine(this.<result>5__2);
43                 }
44                 catch (Exception exception)
45                 {
46                     this.<>1__state = -2;
47                     this.<>t__builder.SetException(exception);
48                     return;
49                 }
50                 this.<>1__state = -2;
51                 this.<>t__builder.SetResult();
52             }
53             [DebuggerHidden]
54             void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
55             {
56             }
57         }
View Code

 

接下來, 看GetInfoAsync()方法, 這個是本身編寫的, 可是實現的細節,最終轉換成了編譯器執行代碼:

        [AsyncStateMachine(typeof(Program.<GetInfoAsync>d__1)), DebuggerStepThrough]
        public static void GetInfoAsync()
        {
            Program.<GetInfoAsync>d__1 <GetInfoAsync>d__ = new Program.<GetInfoAsync>d__1();
            <GetInfoAsync>d__.<>t__builder = AsyncVoidMethodBuilder.Create();
            <GetInfoAsync>d__.<>1__state = -1;
            AsyncVoidMethodBuilder <>t__builder = <GetInfoAsync>d__.<>t__builder;
            <>t__builder.Start<Program.<GetInfoAsync>d__1>(ref <GetInfoAsync>d__); //注意到該代碼, 調用了Start(),也許這就是默認實現的地方
        }

經過查看Start泛型方法的實現, 最終找到了, 該泛型的條件限制於必須實現與 IAsyncStateMachine 接口, 因此經過查看, 該類最終調用了 MoveNext(), 而MoveNext中正

調用了GetAwaiter()。關於Start的實現以下所示:

[SecuritySafeCritical, DebuggerStepThrough, __DynamicallyInvokable]
public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
    if (stateMachine == null)
    {
        throw new ArgumentNullException("stateMachine");
    }
    ExecutionContextSwitcher executionContextSwitcher = default(ExecutionContextSwitcher);
    RuntimeHelpers.PrepareConstrainedRegions();
    try
    {
        ExecutionContext.EstablishCopyOnWriteScope(ref executionContextSwitcher);
        stateMachine.MoveNext();
    }
    finally
    {
        executionContextSwitcher.Undo();
    }
}

剖析MoveNext

 對比IDE中的代碼, 以下所示:

 

總結

await等待的是任務的操做值, 最終返回是異步操做的返回結果。而這一切都是由於編譯器建立了一系列複雜的狀態機制, 以達到其實現。

相關文章
相關標籤/搜索