温故知新,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继续循环。

参考

posted @ 2022-10-30 21:52  TaylorShi  阅读(287)  评论(0编辑  收藏  举报