溫故知新,CSharp遇見異步編程(Async/Await),通過ILSpy反編譯代碼,透過現象看本質

什麼是Async/Await

C#中的Async和Await關鍵字是異步編程的核心。通過這兩個關鍵字,可以使用.NET Framework、.NET Core或Windows運行時中的資源,輕鬆創建異步方法(幾乎與創建同步方法一樣輕鬆)。使用async關鍵字定義的異步方法簡稱爲“異步方法”。

public async Task<int> GetUrlContentLengthAsync()
{
    var client = new HttpClient();

    Task<string> getStringTask =
        client.GetStringAsync("https://docs.microsoft.com/dotnet");

    DoIndependentWork();

    string contents = await getStringTask;

    return contents.Length;
}

void DoIndependentWork()
{
    Console.WriteLine("Working...");
}

異步方法的運行機制

image

  1. 調用方法調用並等待GetUrlContentLengthAsync異步方法。

  2. GetUrlContentLengthAsync可創建HttpClient實例並調用GetStringAsync異步方法以下載網站內容作爲字符串。

  3. GetStringAsync中發生了某種情況,該情況掛起了它的進程。可能必須等待網站下載或一些其他阻止活動。爲避免阻止資源,GetStringAsync會將控制權出讓給其調用方GetUrlContentLengthAsync

    GetStringAsync返回Task<TResult>,其中TResult爲字符串,並且GetUrlContentLengthAsync將任務分配給getStringTask變量。該任務表示調用GetStringAsync的正在進行的進程,其中承諾當工作完成時產生實際字符串值。

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

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

  6. GetUrlContentLengthAsync已運行完畢,可以不受getStringTask的結果影響。接下來,GetUrlContentLengthAsync需要計算並返回已下載的字符串的長度,但該方法只有在獲得字符串的情況下才能計算該值。

    因此,GetUrlContentLengthAsync使用一個await運算符來掛起其進度,並把控制權交給調用GetUrlContentLengthAsync的方法。GetUrlContentLengthAsyncTask<int>返回給調用方。該任務表示對產生下載字符串長度的整數結果的一個承諾。

    如果GetStringAsync(因此getStringTask)在GetUrlContentLengthAsync等待前完成,則控制會保留在GetUrlContentLengthAsync中。如果異步調用過程getStringTask已完成,並且GetUrlContentLengthAsync不必等待最終結果,則掛起然後返回到GetUrlContentLengthAsync將造成成本浪費。

    在調用方法中,處理模式會繼續。在等待結果前,調用方可以開展不依賴於GetUrlContentLengthAsync結果的其他工作,否則就需等待片刻。調用方法等待GetUrlContentLengthAsync,而GetUrlContentLengthAsync等待GetStringAsync

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

  8. GetUrlContentLengthAsync具有字符串結果時,該方法可以計算字符串長度。然後,GetUrlContentLengthAsync工作也將完成,並且等待事件處理程序可繼續使用。在此主題結尾處的完整示例中,可確認事件處理程序檢索並打印長度結果的值。如果你不熟悉異步編程,請花1分鐘時間考慮同步行爲和異步行爲之間的差異。當其工作完成時(第5步)會返回一個同步方法,但當其工作掛起時(第3步和第6步),異步方法會返回一個任務值。在異步方法最終完成其工作時,任務會標記爲已完成,而結果(如果有)將存儲在任務中。

線程

異步方法旨在成爲非阻止操作。異步方法中的await表達式在等待的任務正在運行時不會阻止當前線程。相反,表達式在繼續時註冊方法的其餘部分並將控件返回到異步方法的調用方。

async和await關鍵字不會創建其他線程。因爲異步方法不會在其自身線程上運行,因此它不需要多線程。只有當方法處於活動狀態時,該方法將在當前同步上下文中運行並使用線程上的時間。可以使用Task.Run將佔用大量CPU的工作移到後臺線程,但是後臺線程不會幫助正在等待結果的進程變爲可用狀態

結論

async/await使C#用以實現協程編程的方式,由Task調度器統一調度每一個異步任務的執行,從調度器的線程池中拿出線程來執行,遇到await時線程會被回收,直到異步方法執行完成

async本身沒有任何實際作用,只是在編碼期給程序員提示調用的方法爲異步,真正起作用的是await和Task實例,await遇到Task實例便會將當前線程異步等待,直到執行Task實例的線程結束

主線程執行到await後便停止,不再繼續執行後續代碼,直到輔線程執行完await發起的異步任務

同時發起多個異步任務

把Task實例保存在一個數組中,用await Task.WhenAll(taskArray)等待所有異步任務結束,不可每個任務單獨使用一個await

自定義異步方法

方法標記爲async,用await發起一個Task實例,在Task實例中定義要異步執行的任務

創建Task實例的簡便方式是用Task.Run靜態方法,入參是任務的lambda表達式

private async Task<string> SetSysBatchId(string traceCode)
{
    return await Task.Run(() =>
    {
        return SecurityHelper.md5(traceCode);
    });
}

注意事項

如果沒有使用await關鍵字,那麼該方法就作爲一個同步方法。編譯器將向我們顯示警告,但不會顯示任何錯誤。

async/await本質上只是一個語法糖,它並不產生線程,只是在編譯時把語句的執行邏輯改了,相當於過去我們用callback,這裏編譯器幫你做了

async並不是表明這個方法是異步方法,而是表明這個方法裏有異步調用,真正重要的是await,他會同步等待異步調用的完成

async和await關鍵字不會創建其他線程。因爲異步方法不會在其自身線程上運行,因此它不需要多線程。

如果使用async修飾符將某種方法指定爲異步方法,即啓用以下兩種功能。

  1. 標記的異步方法可以使用await來指定暫停點。await運算符通知編譯器異步方法:在等待的異步過程完成後才能繼續通過該點。同時,控制返回至異步方法的調用方。
  2. 異步方法在await表達式執行時暫停並不構成方法退出,只會導致finally代碼塊不運行。標記的異步方法本身可以通過調用它的方法等待。

如果異步方法未使用await運算符標記暫停點,則該方法會作爲同步方法執行,即使有async修飾符,也不例外

I/O綁定和CPU綁定的不同編程方式:

  1. 如果工作爲I/O綁定,使用async和await(而不使用Task.Run)。不應使用任務並行庫。
  2. 如果工作屬於CPU綁定,並且重視響應能力,使用async和await,但在另一個線程上使用Task.Run生成工作。 如果同時適用於併發和並行,應考慮使用任務並行庫。

類似於線程池工作項對異步操作的封裝,任務是對異步操作的另一種形式的封裝

任務啓動後,通過任務調度器TaskScheduler來調度

.NET中提供兩種任務調度器

  • 一種是線程池任務調度器,也是默認調度器,它會將任務派發給線程池工作者線程;
  • 一種是上下文同步任務調度器,它會將任務派發給當前上下文線程

async方法被編譯成一個狀態機,結合task調度系統,實現語言運行時的協程

csharp語言內部實現了task的調度器,通過線程池來執行task,當一個task wait的時候,就讓出線程,調度別的task在線程上執行

await/async和線程沒有具體的關係,只是編譯器的語法糖,用於在編譯時是否轉換爲狀態機,成爲協程(協程也叫纖程),將await變成一個stackless協程由狀態機實現

衆說紛紜

Async/Await是無棧協程。

Async/Await是一個語法糖,依賴的是線程池的線程切換,保存好請求上下文讓多線程協作,能做的就是更好的發揮異步的作用。

無棧協程的實現依賴於編譯器類似yield return的功能,也就是把原本一個完整的函數拆成狀態機。

異步的本質就是回調。

Async/Await只是一段狀態機代碼的語法糖,實際運行時我們的任務也是通過線程池隊列投遞到線程池工作的,它只是一個多線程複用而已。

個人理解

Await是一個語法糖,是對異步回調的一種封裝,一個異步方法如果前面使用了Await關鍵詞,那麼執行時會等待這個回調的結果,但是異步方法執行不是開啓新的線程,而是複用了線程池任務調度器中線程去執行。

簡單理解

原本函數

void SendRange()
{
    for (int i = 0; i < 100; i++)
    {
        result = Send(i);
    }
}

改寫後

IEnumerable<Action<Action>> SendRange()
{
    for (int i = 0; i < 100; i++)
    {
        yield return (Action callback) => SendWithCallback(i, callback);
    }
}

每次有異步調用的時候,把異步調用的方法包裝成一個東西直接yield返回,然後調用這個方法的時候用專用的異步處理器來調用。

調用SendWithCallback方法不會阻塞。動作完成時會調用callback

void SendWithCallback(int payload, Action callback);

每次callback後調用stateMachine.MoveNext(),直到最後一次調用finalCallback

void IterateEnumeratorWithCallback(IEnumerable<Action<Action>> stateMachine, Action finalCallback)
{
    if (stateMachine.MoveNext())
    {
        stateMachine.Current(() =>
        {
            IterateEnumerableWithCallback(stateMachine, finalCallback);
        });
    }
    else
    {
        finalCallback();
    }
}

void DoSendRange(Action callback)
{
    IEnumerator<Func<Action>> stateMachine = SendRange().GetEnumerator();
    IterateEnumerableWithCallback(stateMachine, callback);
}

最後就可以包裝成

async Task SendRangeAsync()
{
    for (int i = 0; i < 100; i++)
    {
        await SendAsync(i);
    }
}

揭祕異步編程

https://github.com/TaylorShi/HelloAsyncAndAwait

一探究竟

在WinForm程序中定義一個使用了Async和Await關鍵詞的函數方法InitAsync

protected override async void OnLoad(EventArgs e)
{
	Init();
	await InitAsync();
	base.OnLoad(e);
}

private void Init()
{
	Console.WriteLine("123");
}

private async Task InitAsync()
{
	await Task.Run(() =>
	{
		Console.WriteLine("123");
	});
}

我們先來看下Init變成了什麼,它是沒有使用Async/Await關鍵詞的。

.method private hidebysig 
	instance void Init () cil managed 
{
	// Method begins at RVA 0x20a8
	// Header size: 1
	// Code size: 13 (0xd)
	.maxstack 8

	// {
	IL_0000: nop
	// Console.WriteLine("123");
	IL_0001: ldstr "123"
	IL_0006: call void [mscorlib]System.Console::WriteLine(string)
	// }
	IL_000b: nop
	IL_000c: ret
} // end of method Form1::Init

再來看看InitAsync變成了什麼

.method private hidebysig 
	instance class [mscorlib]System.Threading.Tasks.Task InitAsync () cil managed 
{
	.custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = (
		01 00 23 64 65 6d 6f 46 6f 72 46 6f 72 6d 34 38
		2e 46 6f 72 6d 31 2b 3c 49 6e 69 74 41 73 79 6e
		63 3e 64 5f 5f 33 00 00
	)
	.custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = (
		01 00 00 00
	)
	// Method begins at RVA 0x20b8
	// Header size: 12
	// Code size: 56 (0x38)
	.maxstack 2
	.locals init (
		[0] class demoForForm48.Form1/'<InitAsync>d__3'
	)

	IL_0000: newobj instance void demoForForm48.Form1/'<InitAsync>d__3'::.ctor()
	IL_0005: stloc.0
	IL_0006: ldloc.0
	IL_0007: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Create()
	IL_000c: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder demoForForm48.Form1/'<InitAsync>d__3'::'<>t__builder'
	IL_0011: ldloc.0
	IL_0012: ldarg.0
	IL_0013: stfld class demoForForm48.Form1 demoForForm48.Form1/'<InitAsync>d__3'::'<>4__this'
	IL_0018: ldloc.0
	IL_0019: ldc.i4.m1
	IL_001a: stfld int32 demoForForm48.Form1/'<InitAsync>d__3'::'<>1__state'
	IL_001f: ldloc.0
	IL_0020: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder demoForForm48.Form1/'<InitAsync>d__3'::'<>t__builder'
	IL_0025: ldloca.s 0
	IL_0027: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Start<class demoForForm48.Form1/'<InitAsync>d__3'>(!!0&)
	IL_002c: ldloc.0
	IL_002d: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder demoForForm48.Form1/'<InitAsync>d__3'::'<>t__builder'
	IL_0032: call instance class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::get_Task()
	IL_0037: ret
} // end of method Form1::InitAsync

這還沒完,這裏面實際上引入了<InitAsync>d__3,我們看看它定義

.class nested private auto ansi sealed beforefieldinit '<InitAsync>d__3'
	extends [mscorlib]System.Object
	implements [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine
{
	.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
		01 00 00 00
	)
	// Fields
	.field public int32 '<>1__state'
	.field public valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder '<>t__builder'
	.field public class demoForForm48.Form1 '<>4__this'
	.field private valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter '<>u__1'

	// Methods
	.method public hidebysig specialname rtspecialname 
		instance void .ctor () cil managed 
	{
		// Method begins at RVA 0x225d
		// Header size: 1
		// Code size: 8 (0x8)
		.maxstack 8

		// {
		IL_0000: ldarg.0
		// (no C# code)
		IL_0001: call instance void [mscorlib]System.Object::.ctor()
		// }
		IL_0006: nop
		IL_0007: ret
	} // end of method '<InitAsync>d__3'::.ctor

	.method private final hidebysig newslot virtual 
		instance void MoveNext () cil managed 
	{
		.override method instance void [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext()
		// Method begins at RVA 0x2268
		// Header size: 12
		// Code size: 185 (0xb9)
		.maxstack 3
		.locals init (
			[0] int32,
			[1] valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter,
			[2] class demoForForm48.Form1/'<InitAsync>d__3',
			[3] class [mscorlib]System.Exception
		)

		// int num = <>1__state;
		IL_0000: ldarg.0
		IL_0001: ldfld int32 demoForForm48.Form1/'<InitAsync>d__3'::'<>1__state'
		IL_0006: stloc.0
		.try
		{
			// if (num != 0)
			IL_0007: ldloc.0
			IL_0008: brfalse.s IL_000c

			// (no C# code)
			IL_000a: br.s IL_000e

			IL_000c: br.s IL_0066

			// 			awaiter = Task.Run(delegate
			// 			{
			// 				Console.WriteLine("123");
			// 			}).GetAwaiter();
			IL_000e: nop
			IL_000f: ldsfld class [mscorlib]System.Action demoForForm48.Form1/'<>c'::'<>9__3_0'
			IL_0014: dup
			IL_0015: brtrue.s IL_002e

			// (no C# code)
			IL_0017: pop
			// if (!awaiter.IsCompleted)
			IL_0018: ldsfld class demoForForm48.Form1/'<>c' demoForForm48.Form1/'<>c'::'<>9'
			IL_001d: ldftn instance void demoForForm48.Form1/'<>c'::'<InitAsync>b__3_0'()
			IL_0023: newobj instance void [mscorlib]System.Action::.ctor(object, native int)
			IL_0028: dup
			IL_0029: stsfld class [mscorlib]System.Action demoForForm48.Form1/'<>c'::'<>9__3_0'

			IL_002e: call class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Threading.Tasks.Task::Run(class [mscorlib]System.Action)
			IL_0033: callvirt instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter [mscorlib]System.Threading.Tasks.Task::GetAwaiter()
			IL_0038: stloc.1
			IL_0039: ldloca.s 1
			IL_003b: call instance bool [mscorlib]System.Runtime.CompilerServices.TaskAwaiter::get_IsCompleted()
			IL_0040: brtrue.s IL_0082

			// num = (<>1__state = 0);
			IL_0042: ldarg.0
			IL_0043: ldc.i4.0
			IL_0044: dup
			IL_0045: stloc.0
			IL_0046: stfld int32 demoForForm48.Form1/'<InitAsync>d__3'::'<>1__state'
			// <>u__1 = awaiter;
			IL_004b: ldarg.0
			IL_004c: ldloc.1
			IL_004d: stfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter demoForForm48.Form1/'<InitAsync>d__3'::'<>u__1'
			// <InitAsync>d__3 stateMachine = this;
			IL_0052: ldarg.0
			IL_0053: stloc.2
			// <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
			IL_0054: ldarg.0
			IL_0055: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder demoForForm48.Form1/'<InitAsync>d__3'::'<>t__builder'
			IL_005a: ldloca.s 1
			IL_005c: ldloca.s 2
			IL_005e: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::AwaitUnsafeOnCompleted<valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter, class demoForForm48.Form1/'<InitAsync>d__3'>(!!0&, !!1&)
			// return;
			IL_0063: nop
			IL_0064: leave.s IL_00b8

			// awaiter = <>u__1;
			IL_0066: ldarg.0
			IL_0067: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter demoForForm48.Form1/'<InitAsync>d__3'::'<>u__1'
			IL_006c: stloc.1
			// <>u__1 = default(TaskAwaiter);
			IL_006d: ldarg.0
			IL_006e: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter demoForForm48.Form1/'<InitAsync>d__3'::'<>u__1'
			IL_0073: initobj [mscorlib]System.Runtime.CompilerServices.TaskAwaiter
			// num = (<>1__state = -1);
			IL_0079: ldarg.0
			IL_007a: ldc.i4.m1
			IL_007b: dup
			IL_007c: stloc.0
			IL_007d: stfld int32 demoForForm48.Form1/'<InitAsync>d__3'::'<>1__state'

			// awaiter.GetResult();
			IL_0082: ldloca.s 1
			IL_0084: call instance void [mscorlib]System.Runtime.CompilerServices.TaskAwaiter::GetResult()
			// }
			IL_0089: nop
			IL_008a: leave.s IL_00a4
		} // end .try
		catch [mscorlib]System.Exception
		{
			// catch (Exception exception)
			IL_008c: stloc.3
			// <>1__state = -2;
			IL_008d: ldarg.0
			IL_008e: ldc.i4.s -2
			IL_0090: stfld int32 demoForForm48.Form1/'<InitAsync>d__3'::'<>1__state'
			// <>t__builder.SetException(exception);
			IL_0095: ldarg.0
			IL_0096: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder demoForForm48.Form1/'<InitAsync>d__3'::'<>t__builder'
			IL_009b: ldloc.3
			IL_009c: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::SetException(class [mscorlib]System.Exception)
			// return;
			IL_00a1: nop
			IL_00a2: leave.s IL_00b8
		} // end handler

		// <>1__state = -2;
		IL_00a4: ldarg.0
		IL_00a5: ldc.i4.s -2
		IL_00a7: stfld int32 demoForForm48.Form1/'<InitAsync>d__3'::'<>1__state'
		// <>t__builder.SetResult();
		IL_00ac: ldarg.0
		IL_00ad: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder demoForForm48.Form1/'<InitAsync>d__3'::'<>t__builder'
		IL_00b2: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::SetResult()
		// }
		IL_00b7: nop

		IL_00b8: ret
	} // end of method '<InitAsync>d__3'::MoveNext

	.method private final hidebysig newslot virtual 
		instance void SetStateMachine (
			class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine
		) cil managed 
	{
		.custom instance void [mscorlib]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = (
			01 00 00 00
		)
		.override method instance void [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine(class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine)
		// Method begins at RVA 0x2340
		// Header size: 1
		// Code size: 1 (0x1)
		.maxstack 8

		// }
		IL_0000: ret
	} // end of method '<InitAsync>d__3'::SetStateMachine

} // end of class <InitAsync>d__3

篇幅有點長,得慢慢看。

可以看到<InitAsync>d__3是基於System.Object,同時它還繼承了一個接口System.Runtime.CompilerServices.IAsyncStateMachine

狀態機

從接口System.Runtime.CompilerServices.IAsyncStateMachine名字,我們就知道,它意思應該是異步狀態機。

.class interface public auto ansi abstract System.Runtime.CompilerServices.IAsyncStateMachine
{
	.custom instance void __DynamicallyInvokableAttribute::.ctor() = (
		01 00 00 00
	)
	// Methods
	.method public hidebysig newslot abstract virtual 
		instance void MoveNext () cil managed 
	{
		.custom instance void __DynamicallyInvokableAttribute::.ctor() = (
			01 00 00 00
		)
	} // end of method IAsyncStateMachine::MoveNext

	.method public hidebysig newslot abstract virtual 
		instance void SetStateMachine (
			class System.Runtime.CompilerServices.IAsyncStateMachine stateMachine
		) cil managed 
	{
		.custom instance void __DynamicallyInvokableAttribute::.ctor() = (
			01 00 00 00
		)
	} // end of method IAsyncStateMachine::SetStateMachine

} // end of class System.Runtime.CompilerServices.IAsyncStateMachine

它有兩個方法,一個是MoveNext,一個是SetStateMachine

果然,在<InitAsync>d__3中對這兩個方法做了實現。

image

三個字段

除了上面提到的兩個方法,還有三個字段。

// Fields
.field public int32 '<>1__state'
.field public valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder '<>t__builder'
.field public class demoForForm48.Form1 '<>4__this'
.field private valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter '<>u__1'

拋開public class demoForForm48.Form1,剩下的三個是

  • public int32 xxx__State
  • public System.Runtime.CompilerServices.AsyncTaskMethodBuilder xxx__builder
  • public System.Runtime.CompilerServices.TaskAwaiter xxxu__1

設置狀態機

我們來看下SetStateMachine定義

.method private final hidebysig newslot virtual 
	instance void SetStateMachine (
		class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine
	) cil managed 
{
	.custom instance void [mscorlib]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = (
		01 00 00 00
	)
	.override method instance void [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine(class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine)
	// Method begins at RVA 0x2340
	// Header size: 1
	// Code size: 1 (0x1)
	.maxstack 8

	// }
	IL_0000: ret
} // end of method '<InitAsync>d__3'::SetStateMachine

執行流程

回到InitAsync邏輯,我們看看調用層面都做了哪些事情。

.method private hidebysig 
	instance class [mscorlib]System.Threading.Tasks.Task InitAsync () cil managed 
{
	.custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = (
		01 00 23 64 65 6d 6f 46 6f 72 46 6f 72 6d 34 38
		2e 46 6f 72 6d 31 2b 3c 49 6e 69 74 41 73 79 6e
		63 3e 64 5f 5f 33 00 00
	)
	.custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = (
		01 00 00 00
	)
	// Method begins at RVA 0x20b8
	// Header size: 12
	// Code size: 56 (0x38)
	.maxstack 2
	.locals init (
		[0] class demoForForm48.Form1/'<InitAsync>d__3'
	)

	IL_0000: newobj instance void demoForForm48.Form1/'<InitAsync>d__3'::.ctor()
	IL_0005: stloc.0
	IL_0006: ldloc.0
	IL_0007: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Create()
	IL_000c: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder demoForForm48.Form1/'<InitAsync>d__3'::'<>t__builder'
	IL_0011: ldloc.0
	IL_0012: ldarg.0
	IL_0013: stfld class demoForForm48.Form1 demoForForm48.Form1/'<InitAsync>d__3'::'<>4__this'
	IL_0018: ldloc.0
	IL_0019: ldc.i4.m1
	IL_001a: stfld int32 demoForForm48.Form1/'<InitAsync>d__3'::'<>1__state'
	IL_001f: ldloc.0
	IL_0020: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder demoForForm48.Form1/'<InitAsync>d__3'::'<>t__builder'
	IL_0025: ldloca.s 0
	IL_0027: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Start<class demoForForm48.Form1/'<InitAsync>d__3'>(!!0&)
	IL_002c: ldloc.0
	IL_002d: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder demoForForm48.Form1/'<InitAsync>d__3'::'<>t__builder'
	IL_0032: call instance class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::get_Task()
	IL_0037: ret
} // end of method Form1::InitAsync

第一步,先通過AsyncTaskMethodBuilderCreate創建了一個Builder,並賦值給了xxx__builder字段。

System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Create()
System.Runtime.CompilerServices.AsyncTaskMethodBuilder demoForForm48.Form1/'<InitAsync>d__3'::'<>t__builder'

第二步,把xxx__State字段的值初始化爲-1

IL_0019: ldc.i4.m1
IL_001a: stfld int32 demoForForm48.Form1/'<InitAsync>d__3'::'<>1__state'

第三步,通過AsyncTaskMethodBuilderStartxxx__builder對象進行了啓動

IL_0020: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder demoForForm48.Form1/'<InitAsync>d__3'::'<>t__builder'
IL_0025: ldloca.s 0
IL_0027: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Start<class demoForForm48.Form1/'<InitAsync>d__3'>(!!0&)

第四步,通過AsyncTaskMethodBuilderget_Task方法獲取到這次的Task

IL_002d: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder demoForForm48.Form1/'<InitAsync>d__3'::'<>t__builder'
IL_0032: call instance class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::get_Task()

內部原理

現在關鍵的問題在於,在AsyncTaskMethodBuilderCreateStart方法內部到底發生了什麼。

先來看看Create做了啥

.method public hidebysig static 
	valuetype System.Runtime.CompilerServices.AsyncTaskMethodBuilder Create () cil managed 
{
	.custom instance void __DynamicallyInvokableAttribute::.ctor() = (
		01 00 00 00
	)
	// Method begins at RVA 0x145db4
	// Header size: 12
	// Code size: 10 (0xa)
	.maxstack 1
	.locals init (
		[0] valuetype System.Runtime.CompilerServices.AsyncTaskMethodBuilder
	)

	// {
	IL_0000: ldloca.s 0
	// return default(AsyncTaskMethodBuilder);
	IL_0002: initobj System.Runtime.CompilerServices.AsyncTaskMethodBuilder
	IL_0008: ldloc.0
	IL_0009: ret
} // end of method AsyncTaskMethodBuilder::Create

很遺憾,沒做啥。

接下來,我們看看Start做了啥

.method public hidebysig 
	instance void Start<(System.Runtime.CompilerServices.IAsyncStateMachine) TStateMachine> (
		!!TStateMachine& stateMachine
	) cil managed 
{
	.custom instance void System.Security.SecuritySafeCriticalAttribute::.ctor() = (
		01 00 00 00
	)
	.custom instance void System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = (
		01 00 00 00
	)
	.custom instance void __DynamicallyInvokableAttribute::.ctor() = (
		01 00 00 00
	)
	// Method begins at RVA 0x145dcc
	// Header size: 12
	// Code size: 67 (0x43)
	.maxstack 1
	.locals init (
		[0] valuetype System.Threading.ExecutionContextSwitcher
	)

	// if (stateMachine == null)
	IL_0000: ldarg.1
	IL_0001: ldobj !!TStateMachine
	IL_0006: box !!TStateMachine
	IL_000b: brtrue.s IL_0018

	// throw new ArgumentNullException("stateMachine");
	IL_000d: ldstr "stateMachine"
	IL_0012: newobj instance void System.ArgumentNullException::.ctor(string)
	IL_0017: throw

	// ExecutionContextSwitcher ecsw = default(ExecutionContextSwitcher);
	IL_0018: ldloca.s 0
	IL_001a: initobj System.Threading.ExecutionContextSwitcher
	// RuntimeHelpers.PrepareConstrainedRegions();
	IL_0020: call void System.Runtime.CompilerServices.RuntimeHelpers::PrepareConstrainedRegions()
	.try
	{
		// ExecutionContext.EstablishCopyOnWriteScope(ref ecsw);
		IL_0025: ldloca.s 0
		IL_0027: call void System.Threading.ExecutionContext::EstablishCopyOnWriteScope(valuetype System.Threading.ExecutionContextSwitcher&)
		// stateMachine.MoveNext();
		IL_002c: ldarg.1
		IL_002d: constrained. !!TStateMachine
		IL_0033: callvirt instance void System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext()
		// }
		IL_0038: leave.s IL_0042
	} // end .try
	finally
	{
		// ecsw.Undo();
		IL_003a: ldloca.s 0
		IL_003c: call instance void System.Threading.ExecutionContextSwitcher::Undo()
		// }
		IL_0041: endfinally
	} // end handler

	// (no C# code)
	IL_0042: ret
} // end of method AsyncTaskMethodBuilder::Start

翻譯出來就是

Void Start(System.Runtime.CompilerServices.IAsyncStateMachine stateMachine)
{
	if (stateMachine == null)
	throw new ArgumentNullException("stateMachine");
	ExecutionContextSwitcher ecsw = default(ExecutionContextSwitcher);
	RuntimeHelpers.PrepareConstrainedRegions();
	try
	{
		ExecutionContext.EstablishCopyOnWriteScope(ref ecsw);
		stateMachine.MoveNext();
	}
	finally
	{
		ecsw.Undo();
	}
}

其實好像也沒啥,最後就是調用了IAsyncStateMachineMoveNext方法。

關鍵邏輯

接下來,我們要來看看MoveNext方法了,還記得嗎?它的實現是在<InitAsync>d__3裏面。

.method private final hidebysig newslot virtual 
	instance void MoveNext () cil managed 
{
	.override method instance void [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext()
	// Method begins at RVA 0x2268
	// Header size: 12
	// Code size: 185 (0xb9)
	.maxstack 3
	.locals init (
		[0] int32,
		[1] valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter,
		[2] class demoForForm48.Form1/'<InitAsync>d__3',
		[3] class [mscorlib]System.Exception
	)

	// int num = <>1__state;
	IL_0000: ldarg.0
	IL_0001: ldfld int32 demoForForm48.Form1/'<InitAsync>d__3'::'<>1__state'
	IL_0006: stloc.0
	.try
	{
		// if (num != 0)
		IL_0007: ldloc.0
		IL_0008: brfalse.s IL_000c

		// (no C# code)
		IL_000a: br.s IL_000e

		IL_000c: br.s IL_0066

		// 			awaiter = Task.Run(delegate
		// 			{
		// 				Console.WriteLine("123");
		// 			}).GetAwaiter();
		IL_000e: nop
		IL_000f: ldsfld class [mscorlib]System.Action demoForForm48.Form1/'<>c'::'<>9__3_0'
		IL_0014: dup
		IL_0015: brtrue.s IL_002e

		// (no C# code)
		IL_0017: pop
		// if (!awaiter.IsCompleted)
		IL_0018: ldsfld class demoForForm48.Form1/'<>c' demoForForm48.Form1/'<>c'::'<>9'
		IL_001d: ldftn instance void demoForForm48.Form1/'<>c'::'<InitAsync>b__3_0'()
		IL_0023: newobj instance void [mscorlib]System.Action::.ctor(object, native int)
		IL_0028: dup
		IL_0029: stsfld class [mscorlib]System.Action demoForForm48.Form1/'<>c'::'<>9__3_0'

		IL_002e: call class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Threading.Tasks.Task::Run(class [mscorlib]System.Action)
		IL_0033: callvirt instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter [mscorlib]System.Threading.Tasks.Task::GetAwaiter()
		IL_0038: stloc.1
		IL_0039: ldloca.s 1
		IL_003b: call instance bool [mscorlib]System.Runtime.CompilerServices.TaskAwaiter::get_IsCompleted()
		IL_0040: brtrue.s IL_0082

		// num = (<>1__state = 0);
		IL_0042: ldarg.0
		IL_0043: ldc.i4.0
		IL_0044: dup
		IL_0045: stloc.0
		IL_0046: stfld int32 demoForForm48.Form1/'<InitAsync>d__3'::'<>1__state'
		// <>u__1 = awaiter;
		IL_004b: ldarg.0
		IL_004c: ldloc.1
		IL_004d: stfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter demoForForm48.Form1/'<InitAsync>d__3'::'<>u__1'
		// <InitAsync>d__3 stateMachine = this;
		IL_0052: ldarg.0
		IL_0053: stloc.2
		// <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
		IL_0054: ldarg.0
		IL_0055: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder demoForForm48.Form1/'<InitAsync>d__3'::'<>t__builder'
		IL_005a: ldloca.s 1
		IL_005c: ldloca.s 2
		IL_005e: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::AwaitUnsafeOnCompleted<valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter, class demoForForm48.Form1/'<InitAsync>d__3'>(!!0&, !!1&)
		// return;
		IL_0063: nop
		IL_0064: leave.s IL_00b8

		// awaiter = <>u__1;
		IL_0066: ldarg.0
		IL_0067: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter demoForForm48.Form1/'<InitAsync>d__3'::'<>u__1'
		IL_006c: stloc.1
		// <>u__1 = default(TaskAwaiter);
		IL_006d: ldarg.0
		IL_006e: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter demoForForm48.Form1/'<InitAsync>d__3'::'<>u__1'
		IL_0073: initobj [mscorlib]System.Runtime.CompilerServices.TaskAwaiter
		// num = (<>1__state = -1);
		IL_0079: ldarg.0
		IL_007a: ldc.i4.m1
		IL_007b: dup
		IL_007c: stloc.0
		IL_007d: stfld int32 demoForForm48.Form1/'<InitAsync>d__3'::'<>1__state'

		// awaiter.GetResult();
		IL_0082: ldloca.s 1
		IL_0084: call instance void [mscorlib]System.Runtime.CompilerServices.TaskAwaiter::GetResult()
		// }
		IL_0089: nop
		IL_008a: leave.s IL_00a4
	} // end .try
	catch [mscorlib]System.Exception
	{
		// catch (Exception exception)
		IL_008c: stloc.3
		// <>1__state = -2;
		IL_008d: ldarg.0
		IL_008e: ldc.i4.s -2
		IL_0090: stfld int32 demoForForm48.Form1/'<InitAsync>d__3'::'<>1__state'
		// <>t__builder.SetException(exception);
		IL_0095: ldarg.0
		IL_0096: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder demoForForm48.Form1/'<InitAsync>d__3'::'<>t__builder'
		IL_009b: ldloc.3
		IL_009c: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::SetException(class [mscorlib]System.Exception)
		// return;
		IL_00a1: nop
		IL_00a2: leave.s IL_00b8
	} // end handler

	// <>1__state = -2;
	IL_00a4: ldarg.0
	IL_00a5: ldc.i4.s -2
	IL_00a7: stfld int32 demoForForm48.Form1/'<InitAsync>d__3'::'<>1__state'
	// <>t__builder.SetResult();
	IL_00ac: ldarg.0
	IL_00ad: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder demoForForm48.Form1/'<InitAsync>d__3'::'<>t__builder'
	IL_00b2: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::SetResult()
	// }
	IL_00b7: nop

	IL_00b8: ret
} // end of method '<InitAsync>d__3'::MoveNext

很長,再長也要含着淚讀完

void MoveNext()
{
	int num = xxx_state;
	try
	{
		if (num != 0)
		{
			awaiter = Task.Run(delegate
			{
				Console.WriteLine("123");
			}).GetAwaiter();

			if (!awaiter.IsCompleted)
			{
				num = (<>xxx_state = 0);

				xxx_TaskAwaiter = awaiter;

				<InitAsync>d__3 stateMachine = this;

				<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);

				return;
			}
		}
		else
		{
			awaiter = xxx_TaskAwaiter;
			xxx_TaskAwaiter = default(TaskAwaiter);
			num = (<>xxx_state = -1);
		}
		awaiter.GetResult();
	}
	catch (Exception exception)
	{
		<>xxx_state = -2;
		<>t__builder.SetException(exception);
		return;
	}

	<>xxx_state = -2;
	<>t__builder.SetResult();
}

進來後,先對狀態機狀態進行了判斷,初始值是-1,如果不是0,那麼執行Task任務並掛到awaiter,如果任務還沒完成,先把狀態改爲0,然後進入AwaitUnsafeOnCompleted繼續循環。

參考

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