【C# TAP 异步编程】三、async\await的运作机理详解
作者:互联网
本文只是个人笔记,很多错误,欢迎指出。
async\await的运作机理
通过分析一段代码运行来了解async\await的运作机理
namespace MyTask; class Program { public static void Main(string[] args) { Task<int> baconTask = GetUrlContentLengthAsync(3); baconTask.ContinueWith(t => Console.WriteLine(t.Result)); Console.Read(); } static async Task<int> GetUrlContentLengthAsync(int slices) { HttpClient httpClient = new(); //********* 也可以合并起来写*********// // string content = await httpClient.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html"); Task<string> getContent= httpClient.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html"); string content = await getContent; //await getContent反编译后是awaiter = <getContent>5__2.GetAwaiter(); DoInDependentWork(); return content.Length; } static void DoInDependentWork() { Console.WriteLine($"content.Length:Working"); } }
IL反编译后的代码
// MyTask.Program using System; using System.Diagnostics; using System.Net.Http; using System.Runtime.CompilerServices; using System.Threading.Tasks; using MyTask; [System.Runtime.CompilerServices.NullableContext(1)] [System.Runtime.CompilerServices.Nullable(0)] internal class Program { [Serializable] [CompilerGenerated] private sealed class <>c { [System.Runtime.CompilerServices.Nullable(0)] public static readonly <>c <>9 = new <>c(); [System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })] public static Action<Task<int>> <>9__0_0; internal void <Main>b__0_0(Task<int> t) { Console.WriteLine(t.Result); } } [CompilerGenerated] private sealed class <GetUrlContentLengthAsync>d__1 : IAsyncStateMachine { public int <>1__state; [System.Runtime.CompilerServices.Nullable(0)] public AsyncTaskMethodBuilder<int> <>t__builder; public int slices; [System.Runtime.CompilerServices.Nullable(0)] private HttpClient <httpClient>5__1; [System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })] private Task<string> <getContent>5__2; [System.Runtime.CompilerServices.Nullable(0)] private string <content>5__3; [System.Runtime.CompilerServices.Nullable(0)] private string <>s__4; [System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })] private TaskAwaiter<string> <>u__1; private void MoveNext() { int num = <>1__state; int length; try { TaskAwaiter<string> awaiter; if (num != 0) { <httpClient>5__1 = new HttpClient(); <getContent>5__2 = <httpClient>5__1.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html"); awaiter = <getContent>5__2.GetAwaiter(); if (!awaiter.IsCompleted) { num = (<>1__state = 0); <>u__1 = awaiter; <GetUrlContentLengthAsync>d__1 stateMachine = this; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); return; } } else { awaiter = <>u__1; <>u__1 = default(TaskAwaiter<string>); num = (<>1__state = -1); } <>s__4 = awaiter.GetResult(); <content>5__3 = <>s__4; <>s__4 = null; DoInDependentWork(); length = <content>5__3.Length; } catch (Exception exception) { <>1__state = -2; <httpClient>5__1 = null; <getContent>5__2 = null; <content>5__3 = null; <>t__builder.SetException(exception); return; } <>1__state = -2; <httpClient>5__1 = null; <getContent>5__2 = null; <content>5__3 = null; <>t__builder.SetResult(length); } 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); } } public static void Main(string[] args) { Task<int> baconTask = GetUrlContentLengthAsync(3); baconTask.ContinueWith(<>c.<>9__0_0 ?? (<>c.<>9__0_0 = new Action<Task<int>>(<>c.<>9.<Main>b__0_0))); Console.Read(); } [AsyncStateMachine(typeof(<GetUrlContentLengthAsync>d__1))] [DebuggerStepThrough] private static Task<int> GetUrlContentLengthAsync(int slices) { <GetUrlContentLengthAsync>d__1 stateMachine = new <GetUrlContentLengthAsync>d__1(); stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create(); stateMachine.slices = slices; stateMachine.<>1__state = -1; stateMachine.<>t__builder.Start(ref stateMachine); return stateMachine.<>t__builder.Task; } private static void DoInDependentWork() { Console.WriteLine("content.Length:Working"); } }View Code
通过IL代码我们可以分析出整个代码运行过程 如下:
关系图中的数字对应于以下步骤,在调用方法调用异步方法时启动。
-
Main调用方法调用并等待
GetUrlContentLengthAsync
异步方法。 -
GetUrlContentLengthAsync
可创建 HttpClient 实例并调用 GetStringAsync 异步方法以下载网站内容作为字符串。 -
GetStringAsync
中发生了某种情况,该情况挂起了它的进程。 可能必须等待网站下载或一些其他阻止活动。 为避免阻止资源,GetStringAsync
会将控制权出让给其调用方GetUrlContentLengthAsync
。GetStringAsync
返回 Task<TResult>,其中TResult
为字符串,并且GetUrlContentLengthAsync
将任务分配给getStringTask
变量。 该任务表示调用GetStringAsync
的正在进行的进程,其中承诺当工作完成时产生实际字符串值。 -
由于尚未等待
getStringTask
,因此,GetUrlContentLengthAsync
可以继续执行不依赖于GetStringAsync
得出的最终结果的其他工作。 该任务由对同步方法DoIndependentWork
的调用表示。 -
DoIndependentWork
是完成其工作并返回其调用方的同步方法。 -
GetUrlContentLengthAsync
已运行完毕,可以不受getStringTask
的结果影响。 接下来,GetUrlContentLengthAsync
需要计算并返回已下载的字符串的长度,但该方法只有在获得字符串的情况下才能计算该值。因此,
GetUrlContentLengthAsync
使用一个 await 运算符来挂起其进度,并把控制权交给调用GetUrlContentLengthAsync
的方法。GetUrlContentLengthAsync
将Task<int>
返回给调用方。 该任务表示对产生下载字符串长度的整数结果的一个承诺。备注
如果
GetStringAsync
(因此getStringTask
)在GetUrlContentLengthAsync
等待前完成,则控制会保留在GetUrlContentLengthAsync
中。 如果异步调用过程getStringTask
已完成,并且GetUrlContentLengthAsync
不必等待最终结果,则挂起然后返回到GetUrlContentLengthAsync
将造成成本浪费。在调用方法中,处理模式会继续。 在等待结果前,调用方可以开展不依赖于
GetUrlContentLengthAsync
结果的其他工作,否则就需等待片刻。 调用方法等待GetUrlContentLengthAsync
,而GetUrlContentLengthAsync
等待GetStringAsync
。 -
GetStringAsync
完成并生成一个字符串结果。 字符串结果不是通过按你预期的方式调用GetStringAsync
所返回的。 (记住,该方法已返回步骤 3 中的一个任务)。相反,字符串结果存储在表示getStringTask
方法完成的任务中。 await 运算符从getStringTask
中检索结果。 赋值语句将检索到的结果赋给contents
。 -
当
GetUrlContentLengthAsync
具有字符串结果时,该方法可以计算字符串长度。 然后,GetUrlContentLengthAsync
工作也将完成,并且等待事件处理程序可继续使用。 在此主题结尾处的完整示例中,可确认事件处理程序检索并打印长度结果的值。 如果你不熟悉异步编程,请花 1 分钟时间考虑同步行为和异步行为之间的差异。 当其工作完成时(第 5 步)会返回一个同步方法,但当其工作挂起时(第 3 步和第 6 步),异步方法会返回一个任务值。 在异步方法最终完成其工作时,任务会标记为已完成,而结果(如果有)将存储在任务中。
反编译后我们可以看到async\await的运作机理主要分为分异步状态机和等待器,现在我主要来讲解着两部分的运行机制。
1、异步状态机
(1)异步状态机 始状态是-1;
(2)运行第一个【等待器】 期间异步状态机的状态是0。
(3)第一个【等待器】完成后异步状态机状态恢复-1。
(4)运行等二个【等待器】期间 异步状态机的状态是1,后面【等待器】以此类推。
(5)当所有的等待器都完成后,异步状态机的状态为-2。
异步状态机的组成
2、等待器
(1)有返回值的等待器,它返回值就是异步方法public Task<TResult> GetStringAsync(string? requestUri)返回值Task<TResult> 中的TResult。
此例子 中httpClient.GetStringAsync() 返回值是Task<string>。
所以等待器 TaskAwaiter的返回值是 string(Task<string>中的string)。
3、通过IL代码,查看异步的执行过程,以及最后是如何把任务交给线程池运行
异步代码大概运行顺序:
步骤1、主程序main调用异步f方法GetUrlContentLengthAsync()
步骤2、GetUrlContentLengthAsync();完成状态机初始化,然后调用状态机中异步任务方法构建器 中的开始函数:stateMachine.<>t__builder.Start(ref stateMachine);
步骤3、运行Start()函数中的AsyncMethodBuilderCore.Start(ref stateMachine);语句,保存当前线程的ExecutionContext(执行状态机)和SynchronizationContext(同步状态机),然后执行stateMachine.MoveNext();
该语句调用状态机的 MoveNext()方法;步骤4 开始进入状态机的MoveNext()方法;
步骤4、生成等待器 awaiter = <getContent>5__2.GetAwaiter(); 将状态机更改成0;然后运行<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
步骤5开始, 就是任务是一步一步被送入线程池运行,然后程序把控制权还给调用程序main。
步骤5、运行AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine, ref m_task);
步骤6、生成状态机盒子,将状态机和当前线程执行上下文 封装入状态机 代码如下:
IAsyncStateMachineBox stateMachineBox = GetStateMachineBox(ref stateMachine, ref taskField);
然后运行AwaitUnsafeOnCompleted(ref awaiter, stateMachineBox);
步骤7、执行TaskAwaiter.UnsafeOnCompletedInternal(Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter).m_task, box, true);
return;
步骤8、运行OutputWaitEtwEvents(task, stateMachineBox.MoveNextAction,将任务和状态机的moveNext()封装成新的 委托。然后task.SetContinuationForAwait()运行该委托。
捕获当前同步上下文,
步骤9、运行AwaitTaskContinuation.UnsafeScheduleAction(continuationAction, this);
步骤10、ThreadPool.UnsafeQueueUserWorkItemInternal(awaitTaskContinuation, true);这一步将步骤8生成的委托交给线程池。线程池 会分配一个线程来运行异步任务,完成任务后会执行根据这个委托中的moveNext(),因为执行上下文已经一起封装进去了,所以
线程能准确找到moveNext()。 到此为止步骤4的函数执行完毕。程序继续执行 骤4后的return;将控制权交给调用函数main。
当线程完成任务后会继续执行调用状态机的moveNext(),直到所有等待器执行完毕。
参考: https://www.cnblogs.com/lsxqw2004/p/4922374.html
标签:__,异步,GetStringAsync,TAP,stateMachine,GetUrlContentLengthAsync,C#,await,状态机 来源: https://www.cnblogs.com/cdaniu/p/15703945.html