2019年5月31日 星期五

.NET Core 主控台應用程式的 ILogger 寫入日誌使用練習

.NET Core 主控台應用程式的 ILogger 寫入日誌使用練習

當在開發 .NET Core 主控台類型的應用程式的時候,可以使用 .NET Core 所提供的 Microsoft.Extensions.Logging 套件,並且透過 Microsoft.Extensions.DependencyInjection 套件,使用相依性注入的方式,取得 ILogger 型別的實作物件,就可以進行寫入日誌的工作。因此,在這裡需要先建立一個 .NET Core 主控台應用程式專案,並且在這個專案內加入底下三個 NuGet 套件。
這篇文章中的範例原始碼,可以從 GitHub 取得
Microsoft.Extensions.DependencyInjection
Microsoft.Extensions.Logging
Microsoft.Extensions.Logging.Console
當完成所需要用到的 NuGet 套件之後,就可以建立一個類別,並且在這個類別中,使用建構式注入的方式,將 ILogger 介面所需要用到的實作類別物件注入到建構函式內;而且在該類別中,也設計一個方法 Method,在這個方法內將會使用剛剛使用建構函式注入進來的 ILogger 物件,將需要寫入到日誌的內容,使用 logger.LogInformation("現在正在執行 Method 方法"); 方法寫入。
如此,當透過 .NET Core 的相依性注入容器取得一個 MyClass 實作物件的時候,就會自動產生一個 ILogger 的實作物件,並且可以透過這個 ILogger 物件來進行寫入到日誌的動作了。
C Sharp / C#
class MyClass
{
    private readonly ILogger<MyClass> logger;

    public MyClass(ILogger<MyClass> logger)
    {
        this.logger = logger;
    }
    public void Method()
    {
        logger.LogInformation("現在正在執行 Method 方法");
    }
}
現在,要回到主控台應用程式的進入點,也就是 Main 這個方法,在這個方法內,先要進行 DI 容器的設定,所以,先要建立一個 ServiceCollection 物件,在這裡將會把相關要對於 DI 容器設定的工作,寫在 ConfigureServices 這個方法內;在這個方法裡面,首先使用 AddLogging 方法註冊 Microsoft.Extensions.Logging 服務需要對 Console 做輸出,並且註冊 MyClass 這個類別,因此,只要對這個 DI 容器進行解析 MyClass 類別,就可以產生一個新的類別出來。
完成了 DI 容器的註冊工作,現在可以透過 IServiceCollection.BuildServiceProvider 方法,建立起這個專案要使用的 DI 容器 Container,也就是 IServiceProvider。
當需要使用到 MyClass 類別的物件,就可以透過 IServiceProvider.GetService 方法,注入這個類別的物件,現在,就可以呼叫這個物件的 Method() 方法,此時,螢幕就會出現底下內容。
Press any key for continuing...
info: CoreLogging.MyClass[0]
      現在正在執行 Method 方法
fail: CoreLogging.Program[0]
      Program 類別內發生了意外異常
在上面的第一個日誌輸出,標示著為 CoreLogging.MyClass ,現在,若想要在這個 Program 類別內,也想要寫入一筆紀錄到日誌內,可以使用 var logger = serviceProvider.GetService<ILogger<Program>>(); 敘述,取得日誌物件,接著呼叫 logger.LogError("Program 類別內發生了意外異常"); 方法,就會看到上面輸出的第二個日誌內容,這是一個 fail 的紀錄,並且標示著 CoreLogging.Program
C Sharp / C#
class Program
{
    static void Main(string[] args)
    {
        var serviceCollection = new ServiceCollection();
        ConfigureServices(serviceCollection);

        var serviceProvider = serviceCollection.BuildServiceProvider();

        var myClass = serviceProvider.GetService<MyClass>();

        myClass.Method();

        var logger = serviceProvider.GetService<ILogger<Program>>();
        logger.LogError("Program 類別內發生了意外異常");

        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
    }
    private static void ConfigureServices(IServiceCollection services)
    {
        services.AddLogging(configure => configure.AddConsole())
                  .AddTransient<MyClass>();
    }
}




2019年5月30日 星期四

.NET 編譯器對 C# 使用了 async / await 關鍵字程式碼,做了什麼事情

.NET 編譯器對 C# 使用了 async / await 關鍵字程式碼,做了什麼事情

測試環境與測試原始碼說明

在這篇文章中,將要來徹底解密 C# 內的非同步工作設計使用到的語法糖 Syntactic sugar,也就是 async 與 await ,為什麼說這是語法糖呢?因為當在 C# 程式語言中使用了這兩個關鍵字之後,C# 編譯器將會產生出與多程式碼,以便可以順利執行非同步計算需求執行所需要的相關程式碼,而這些程式碼對於程式設計師而言,是不需要額外再來撰寫的;也就是說,若沒有這樣的語法糖機制,對於要設計出一個非同步計算的程式碼,是需要再度加入更多需要程式碼。所以,在這篇文章中將會透過這樣的還原技術,了解到當可以在 C# 5.0 以上的版本,可以輕鬆來撰寫 async / await 程式碼,但當使用在 C# 4.0 程式語言版本的時候,因為沒有辦法使用這樣的關鍵字,究竟要透過怎麼樣的程式碼,才能夠時做出這樣的方法。
首先,對於要了解 C# 編譯器對於 async await 做了甚麼事情,需要有一個程式碼並且有進行非同步方法呼叫的程式碼,因此,這篇文章將會使用底下的程式碼作為講解範例。在這個範例程式碼,程式進入點函式 Main 方法,將會以封鎖執行緒的方式等候 MainAsync 方法執行完成,而在 MainAsync 方法內,將會使用 await 運算子等候 MyAsync 這個非同步方法結束,而在 MyAsync 方法內,則會使用 await 運算子,等候 Task.Delay(5000) 這個非同步工作完成,也就是等候 5 秒鐘。
C Sharp / C#
class Program
{
    static void Main(string[] args)
    {
        MainAsync(args).GetAwaiter().GetResult();
    }
    static async Task MainAsync(string[] args)
    {
        await MyAsync();
    }
    static async Task MyAsync()
    {
        await Task.Delay(5000);
        Console.WriteLine("Hello World!");
    }
}
請使用 Visual Studio 建立一個新的主控制台應用專案 Console Application ,將上述的程式碼進行編譯與建置,若沒有發生任何問題,此時可以啟動 ILSpy 來查看這個專案組件的反组譯後的內容。在底下的螢幕截圖將會把 ConsoleApp2 這個組件 Assembly 打開來,並且切換到 [CosoleApp2] > [CosoleApp2] > [Program] 節點下所看到的內容,在 ILSpy 視窗的右半部將會看到 Progam 這個命名空間內使用的 C# 反组譯程式碼內容。
其中,在上面的 ILSpy 視窗中,有兩個紅色圓圈標記,標記1 的地方,可以切換要查看的反组譯內容為 C# 或者使用編譯過後的 IL 中間語言 Intermediate Language 來呈現,甚是可以選擇使用 IL 中間語言 Intermediate Language 與 C# 程式語言來同時顯示。
對於 標記2 的地方,是可以顯示出使用不同的 C# 程式語言版本來顯示
現在可以嘗試看看切換 標記2 的下拉選單 Combobox 控制項,選擇 [C# 4.0 / VS 2010] 這個選項,因為 async / await 這兩個關鍵字是在 C# 5.0 才推出來的功能,若在 C# 4.0 的時候,想要自己設計出這樣的功能,應該要如何撰寫出這樣的程式碼呢?下面螢幕截圖將會是把這個範例原始碼,經過編譯與反组譯之後,使用 C# 4.0 程式語言來呈現的結果。
當點選了 [Program] 節點之後,將右方的 MyAsync 方法展開,竟然看到全然不同的程式碼,甚至相當的陌生,並不瞭解這個 MyAsync 方法內在做了甚麼事情,還有,之前在這個方法內宣告與定義的區域變數與使用 await 等候非同步工作的程式碼,全然都不見了。
另外,從底下螢幕截圖中,可以看到編譯器產生了兩個新的類別,分別是 <MainAsync>d__1 與 <MyAsync>d__2,因為這兩個類別分別有標示 [CompilerGenerated] 屬性宣告,這個屬性是用來 區別編譯器產生的項目與使用者產生的項目,更多資訊可以參考 CompilerGenerated

編譯器 Compiler 處理 1 : 將 async 方法變更成為沒有使用 async 修飾詞與方法改用狀態機物件

首先, 使用 C# 程式語言撰寫的非同步方法,也就是 async Task MyAsync(),其程式碼內容如下所示,這裡有宣告兩個區域變數,一個是 foo 整數型別,另外一個是 message 字串型別,在呼叫非同步方法前,會先針對這兩個區域變數做些初始化定義,並且輸出一段文字,接著會有使用 await 運算子來呼叫與等候非同步工作 Task.Delay 方法結束執行,最後將會做些其他運算與輸出文字。
可是,從上圖中可以看到當使用 ILSpy 反组譯工具所得到的 C# 4.0 的程式碼,對於原先的 async Task MyAsync() 將被修改成為 Task MyAsync() ,也就是 async 這個修飾詞被移除了,另外該 MyAsync 方法裡面的內容也全部被替換了,換成一個很奇怪的類別 <MyAsnyc>d__2 類別所生成的物件, stateMachine 與針對這個 stateMachine 物件所作的相關操作。
其中,這個 <MyAsnyc>d__2 類別,就是編譯器所產生的一個類別,他是一個狀態機 State Machine,從維基百科可以得到這個名詞的定義:有限狀態機(英語:finite-state machine,縮寫:FSM)又稱有限狀態自動機,簡稱狀態機,是表示有限個狀態以及在這些狀態之間的轉移和動作等行為的數學模型。這裡使用了 AsyncTaskMethodBuilder.Create() 工廠方法建立一個狀態機物件,接著使用這個敘述 stateMachine.<>1__state = -1; 設定該狀態機的狀態值為 -1。最後將會使用 <>t__builder.Start(ref stateMachine);敘述開始啟動這個狀態機,也就是會在這裡進行非同步的相關處理工作,最後將會執行 return stateMachine.<>t__builder.Task; 回到當初呼叫 MyAsync 方法的呼叫端。
C Sharp / C#
static async Task MyAsync()
{
    int foo;
    string message = "";
    foo = 10;
    message = "呼叫非同步方法前";
    Console.WriteLine(message);
    await Task.Delay(5000);
    foo = foo+168;
    message = "呼叫非同步方法後";
    Console.WriteLine(message+ foo.ToString());
}
從上面的敘述與程式碼,可以瞭解到這句話的意思,當程式執行到 await 運算子的時候,將會開始非同步執行所指定的非同步方法,並且立即返回到呼叫端,從這個反组譯的程式就可以一目了然這句話的意義。
不過,此時,await 後面的指定的非同步工作,正在並行或者平行在執行中,而這個 MyAsync 方法回傳一個 Task 物件,若呼叫端一樣有使用 await 運算子來等待這個 MyAsync 非同步工作,此時,因為 MyAsync 也尚未完成,所以呼叫端因為 await 運算子,也會返回到呼叫端 > 呼叫端 的函式內。
只要 await 所指定的非同步工作執行完畢後,就會透過狀態機類別內的程式碼,讓 await 之後的敘述繼續執行,至於為什麼會這樣呢?底下有更完整的介紹。
底下將會是經過編譯器所修改過的 MyAsync 方法,不過將會使用 C# 4.0 的語法來呈現出來,現在可以比對兩個 MyAsync 方法的差異。
C Sharp / C#
private static Task MyAsync()
{
    <MyAsync>d__2 stateMachine = new <MyAsync>d__2();
    stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
    stateMachine.<>1__state = -1;
    AsyncTaskMethodBuilder <>t__builder = stateMachine.<>t__builder;
    <>t__builder.Start(ref stateMachine);
    return stateMachine.<>t__builder.Task;
}

編譯器 Compiler 處理 1 : async 方法內的變數,將會變成狀態機類別內的欄位

在這裡將會來了解剛剛提到的狀態機類別,他將會做到底下的事情:
  • 使用不同的狀態碼,來處理不同需求,並且使用 MoveNext 進入到狀態機
  • async 方法內的變數,將會變成狀態機類別內的欄位
  • await 關鍵字前後的程式碼,會拆成兩塊區段
  • 使用 callback 來繼續非同步程序後的程式碼
底下的程式碼將會呈現出 MyAsyc 方法所會用到的狀態機類別程式碼,請注意,這個類別 <MyAsync>d__2將會是由編譯器所產生出來的,不是自己撰寫出來的。
C Sharp / C#
private sealed class <MyAsync>d__2 : IAsyncStateMachine
{
    public int <>1__state;
    public AsyncTaskMethodBuilder <>t__builder;
    private int <foo>5__1;
    private string <message>5__2;
    private TaskAwaiter <>u__1;
    private void MoveNext()
    {
        int num = <>1__state;
        try
        {
            TaskAwaiter awaiter;
            if (num != 0)
            {
                <message>5__2 = "";
                <foo>5__1 = 10;
                <message>5__2 = "呼叫非同步方法前";
                Console.WriteLine(<message>5__2);
                awaiter = Task.Delay(5000).GetAwaiter();
                if (!awaiter.IsCompleted)
                {
                    <>1__state = 0;
                    <>u__1 = awaiter;
                    <MyAsync>d__2 stateMachine = this;
                    <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
                    return;
                }
            }
            else
            {
                awaiter = <>u__1;
                <>u__1 = default(TaskAwaiter);
                <>1__state = -1;
            }
            awaiter.GetResult();
            <foo>5__1 += 168;
            <message>5__2 = "呼叫非同步方法後";
            Console.WriteLine(<message>5__2 + <foo>5__1.ToString());
        }
        catch (Exception exception)
        {
            <>1__state = -2;
            <>t__builder.SetException(exception);
            return;
        }
        <>1__state = -2;
        <>t__builder.SetResult();
    }
    void IAsyncStateMachine.MoveNext()
    {
        //ILSpy generated this explicit interface implementation from .override directive in MoveNext
        this.MoveNext();
    }
    [DebuggerHidden]
    private void SetStateMachine(IAsyncStateMachine stateMachine)
    {
    }
    void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
    {
        //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
        this.SetStateMachine(stateMachine);
    }
}

async 方法內的變數,將會變成狀態機類別內的欄位

在這個類別一開始,將會看到底下的欄位宣告,其中,原先在 MyAsync 方法內宣告的區域變數,都會在這裡變成了該類別的欄位 Field,例如原先的 foo 區域整數變數,將會變成 <foo>5__1 欄位,而原先的 message 區域字串變數,將會變成 <message>5__2 欄位。
另外,關於 <>1__state 這個整數欄位,將會表示這個狀態機現在的狀態值,在前一段說明將會得知,這個狀態被建立、啟動之前,這個 <>1__state 狀態值將會為 -1。
C Sharp / C#
public int <>1__state;
public AsyncTaskMethodBuilder <>t__builder;
private int <foo>5__1;
private string <message>5__2;
private TaskAwaiter <>u__1;

使用不同的狀態碼,來處理不同需求,並且使用 MoveNext 進入到狀態機

這個狀態機內的方法 MoveNext ,是整個狀態機運作的主幹地方,只要當這個狀態機的狀態值 <>1__state有所變動的時候,將會再度呼叫 MoveNext 這個方法,在這個方法內,將會依據當時狀態機的狀態值,決定要執行那些程式碼。因此,整個非同步的方法都會反覆透過變更不同的狀態碼,接著呼叫這個 MoveNext 方法。
所以,下在當在開發 .NET 專案程式,若看到例外異常拋出的時候,看到的是有 MoveNext 的函式名稱,不用懷疑,那就是非同步方法中出了問題了。
C Sharp / C#
private void MoveNext()
{
    int num = <>1__state;
    try
    {
        TaskAwaiter awaiter;
        if (num != 0)
        {
            <message>5__2 = "";
            <foo>5__1 = 10;
            <message>5__2 = "呼叫非同步方法前";
            Console.WriteLine(<message>5__2);
            awaiter = Task.Delay(5000).GetAwaiter();
            if (!awaiter.IsCompleted)
            {
                <>1__state = 0;
                <>u__1 = awaiter;
                <MyAsync>d__2 stateMachine = this;
                <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
                return;
            }
        }
        else
        {
            awaiter = <>u__1;
            <>u__1 = default(TaskAwaiter);
            <>1__state = -1;
        }
        awaiter.GetResult();
        <foo>5__1 += 168;
        <message>5__2 = "呼叫非同步方法後";
        Console.WriteLine(<message>5__2 + <foo>5__1.ToString());
    }
    catch (Exception exception)
    {
        <>1__state = -2;
        <>t__builder.SetException(exception);
        return;
    }
    <>1__state = -2;
    <>t__builder.SetResult();
}

await 關鍵字前後的程式碼,會拆成兩塊區段

現在,還需要繼續來了解上一個階段顯示出來的 MoveNext() 方法程式碼,首先,在這個 MoveNext 方法程式碼中,將會看到原先 MyAsync 的相關程式碼,其中,在 await 關鍵字之前與之後的程式碼將會切割成為兩個區塊,分別使用不同的狀態機之狀態碼來執行。
當第一次啟動與進入到狀態機之後,因為開始的狀態碼為 -1 ,所以,當執行了 MoveNext 方法的時候, if (num != 0) 這個運算是將會得到 true,所以,將會執行 if 成立的程式碼區塊,也就是底下的程式碼。
從這裡可以大致看的出來,在呼叫 awaiter = Task.Delay(5000).GetAwaiter(); 敘述之前的程式碼,就是 MyAsync 原先 C# 程式碼中的 await 關鍵字之前的程式碼;在執行剛剛提到的程式碼,將會進行非同步的呼叫,此時將會進入到休息 5 秒鐘的非同步工作,而且馬上緊接著會檢查該工作是否已經完成,使用 if (!awaiter.IsCompleted) 這個敘述來做到。
會有這樣的設計是因為有些非同步工作因為在某些情況,一呼叫的話,就會立即結束,不會有任何非同步作業執行,所以,當發生這樣的情況,就會繼續執行 if {} else {} 之後的程式碼,立即執行 awaiter.GetResult(); 敘述,得到非同步工作的執行結果,在這裡將會繼續執行原先 MyAsync 方法內的 await 之後的程式碼。
最後會執行 <>1__state = -2; <>t__builder.SetResult(); 這兩個敘述,設定狀態碼為 -2 表示非同步工作結束執行與呼叫 SetResult 方法,設定非同步工作結果。

使用 callback 來繼續非同步程序後的程式碼

若非同步工作尚未完成,變更該狀態機的狀態值為 0 (使用這個敘述 <>1__state = 0;),並且註冊一個 callback 委派方法 (使用這個敘述 <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);),該註冊的委派方法會當非同步工作執行完成之後被呼叫,也就是會再度執行 MoveNext 這個方法。所以,當這個 callback 方法被呼叫的時候,在這個 MoveNext 方法內,因為狀態值為 0,所以,將會執行 else 區塊的程式碼,在這裡,狀態值將會被設定成為 -1,就此結束了這個狀態機運行。
另外,將會繼續執行 就會繼續執行 if {} else {} 之後的程式碼,立即執行 awaiter.GetResult(); 敘述,得到非同步工作的執行結果,在這裡將會繼續執行原先 MyAsync 方法內的 await 之後的程式碼。
最後會執行 <>1__state = -2; <>t__builder.SetResult(); 這兩個敘述,設定狀態碼為 -2 表示非同步工作結束執行與呼叫 SetResult 方法,設定非同步工作結果。
C Sharp / C#
if (num != 0)
{
    <message>5__2 = "";
    <foo>5__1 = 10;
    <message>5__2 = "呼叫非同步方法前";
    Console.WriteLine(<message>5__2);
    awaiter = Task.Delay(5000).GetAwaiter();
    if (!awaiter.IsCompleted)
    {
        <>1__state = 0;
        <>u__1 = awaiter;
        <MyAsync>d__2 stateMachine = this;
        <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
        return;
    }
}
else
{
    awaiter = <>u__1;
    <>u__1 = default(TaskAwaiter);
    <>1__state = -1;
}
awaiter.GetResult();
<foo>5__1 += 168;
<message>5__2 = "呼叫非同步方法後";
Console.WriteLine(<message>5__2 + <foo>5__1.ToString());

當在 MoveNext 方法內遇到例外異常

此時,狀態碼將會設定成為 -2,使用 <>t__builder.SetException(exception); 敘述設定該例外異常,結束這個非同步工作運行。
C Sharp / C#
catch (Exception exception)
{
    <>1__state = -2;
    <>t__builder.SetException(exception);
    return;
}
<>1__state = -2;
<>t__builder.SetResult();

完整的狀態機程式碼

底下是完整的 <MyAsync>d__2 類別所有程式碼
C Sharp / C#
[CompilerGenerated]
private sealed class <MyAsync>d__2 : IAsyncStateMachine
{
    public int <>1__state;
    public AsyncTaskMethodBuilder <>t__builder;
    private int <foo>5__1;
    private string <message>5__2;
    private TaskAwaiter <>u__1;
    private void MoveNext()
    {
        int num = <>1__state;
        try
        {
            TaskAwaiter awaiter;
            if (num != 0)
            {
                <message>5__2 = "";
                <foo>5__1 = 10;
                <message>5__2 = "呼叫非同步方法前";
                Console.WriteLine(<message>5__2);
                awaiter = Task.Delay(5000).GetAwaiter();
                if (!awaiter.IsCompleted)
                {
                    <>1__state = 0;
                    <>u__1 = awaiter;
                    <MyAsync>d__2 stateMachine = this;
                    <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
                    return;
                }
            }
            else
            {
                awaiter = <>u__1;
                <>u__1 = default(TaskAwaiter);
                <>1__state = -1;
            }
            awaiter.GetResult();
            <foo>5__1 += 168;
            <message>5__2 = "呼叫非同步方法後";
            Console.WriteLine(<message>5__2 + <foo>5__1.ToString());
        }
        catch (Exception exception)
        {
            <>1__state = -2;
            <>t__builder.SetException(exception);
            return;
        }
        <>1__state = -2;
        <>t__builder.SetResult();
    }
    void IAsyncStateMachine.MoveNext()
    {
        //ILSpy generated this explicit interface implementation from .override directive in MoveNext
        this.MoveNext();
    }
    [DebuggerHidden]
    private void SetStateMachine(IAsyncStateMachine stateMachine)
    {
    }
    void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
    {
        //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
        this.SetStateMachine(stateMachine);
    }
}

若使用 C# 5.0 / VS 2012 來查看反组譯結果

現在,切換到 C# 5.0 / VS 2012 的模式下,來檢視 Program 命名空間的內容,此時,將會看到與之前使用 C# 4.0 / VS 2010 模式下有些不同,因為,在 C# 5.0 是可以使用 async / await 這兩個關鍵字。
在這裡 MyAsync 非同步方法可以看到完整的程式碼,並且在下圖的右方也看不到狀態機的類別出現在 Program 命名空間內。
現在,切換 ILSpy 上方的下拉選單,切換 C# 成為 IL with C#,並且點選 MyAsync() : Task 節點,將會看到右方出現這個 MyAsync 非同步方法的 IL 程式碼;若慢慢地觀看這個 IL 程式碼,將會看到在這裡將會做到的是建立一個狀態機、設定狀態碼為 -1、啟動狀態機、並且回傳一個工作,也就是切換成為 C# 4.0 / VS 2010 中所看到的 C# 程式碼所做的事情。
底下是 MyAsync 的 IL with C# 程式碼
.method private hidebysig static 
    class [System.Runtime]System.Threading.Tasks.Task MyAsync () cil managed 
{
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [System.Runtime]System.Type) = (
        01 00 21 43 6f 6e 73 6f 6c 65 41 70 70 32 2e 50
        72 6f 67 72 61 6d 2b 3c 4d 79 41 73 79 6e 63 3e
        64 5f 5f 32 00 00
    )
    .custom instance void [System.Diagnostics.Debug]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = (
        01 00 00 00
    )
    // Method begins at RVA 0x20bc
    // Code size 52 (0x34)
    .maxstack 2
    .locals init (
        [0] class ConsoleApp2.Program/'<MyAsync>d__2',
        [1] valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder
    )

    IL_0000: newobj instance void ConsoleApp2.Program/'<MyAsync>d__2'::.ctor()
    IL_0005: stloc.0
    IL_0006: ldloc.0
    IL_0007: call valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Create()
    IL_000c: stfld valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder ConsoleApp2.Program/'<MyAsync>d__2'::'<>t__builder'
    IL_0011: ldloc.0
    IL_0012: ldc.i4.m1
    IL_0013: stfld int32 ConsoleApp2.Program/'<MyAsync>d__2'::'<>1__state'
    IL_0018: ldloc.0
    IL_0019: ldfld valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder ConsoleApp2.Program/'<MyAsync>d__2'::'<>t__builder'
    IL_001e: stloc.1
    IL_001f: ldloca.s 1
    IL_0021: ldloca.s 0
    IL_0023: call instance void [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Start<class ConsoleApp2.Program/'<MyAsync>d__2'>(!!0&)
    IL_0028: ldloc.0
    IL_0029: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder ConsoleApp2.Program/'<MyAsync>d__2'::'<>t__builder'
    IL_002e: call instance class [System.Runtime]System.Threading.Tasks.Task [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::get_Task()
    IL_0033: ret
} // end of method Program::MyAsync
現在,切換 ILSpy 上方的下拉選單,切換 IL with C# 成為 C#,並且點選 <MyAsync>d__2 節點,將會看到右方出現這個 狀態機 C# 程式碼
C Sharp / C#
[CompilerGenerated]
private sealed class <MyAsync>d__2 : IAsyncStateMachine
{
    public int <>1__state;
    public AsyncTaskMethodBuilder <>t__builder;
    private int <foo>5__1;
    private string <message>5__2;
    private TaskAwaiter <>u__1;
    private void MoveNext()
    {
        int num = <>1__state;
        try
        {
            TaskAwaiter awaiter;
            if (num != 0)
            {
                <message>5__2 = "";
                <foo>5__1 = 10;
                <message>5__2 = "呼叫非同步方法前";
                Console.WriteLine(<message>5__2);
                awaiter = Task.Delay(5000).GetAwaiter();
                if (!awaiter.IsCompleted)
                {
                    <>1__state = 0;
                    <>u__1 = awaiter;
                    <MyAsync>d__2 stateMachine = this;
                    <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
                    return;
                }
            }
            else
            {
                awaiter = <>u__1;
                <>u__1 = default(TaskAwaiter);
                <>1__state = -1;
            }
            awaiter.GetResult();
            <foo>5__1 += 168;
            <message>5__2 = "呼叫非同步方法後";
            Console.WriteLine(<message>5__2 + <foo>5__1.ToString());
        }
        catch (Exception exception)
        {
            <>1__state = -2;
            <>t__builder.SetException(exception);
            return;
        }
        <>1__state = -2;
        <>t__builder.SetResult();
    }

    void IAsyncStateMachine.MoveNext()
    {
        //ILSpy generated this explicit interface implementation from .override directive in MoveNext
        this.MoveNext();
    }

    [DebuggerHidden]
    private void SetStateMachine(IAsyncStateMachine stateMachine)
    {
    }

    void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
    {
        //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
        this.SetStateMachine(stateMachine);
    }
}

結論

當在 C# 5.0 以上的版本來開發 C# 程式碼的時候,想要執行非同步工作,可以使用 async 修飾詞 與 await 運算子,這樣編譯器就會自動幫助產生出許多在執行非同步工作時候會用到的程式碼,也就是會產生一個狀態機類別。
另外,原先使用 async 修飾詞所設計的非同步方法,將會把函式內的敘述與區域變數,完全轉移到狀態機類別內,並且把這個非同步方法替換成為產生一個有限狀態機物件與進行初始化和啟動狀態機的程式碼。
+

原先的非同步方法內的敘述,將會以 await 運算子作為分隔界線,將其前後敘述拆成兩塊並放在狀態機內,由不同的狀態碼來執行這些程式碼。
因此,async await 這個語法糖在非同步方法呼叫的設計過程中,扮演者非常重要的角色,而且非常的好用。