据我所知,如果从迭代器块内部使用yield关键字,它会将控制流返回给调用代码,当迭代器再次被调用时,它会从停止的地方开始.

此外,await不仅等待被调用者,而且将控制权返还给调用者,只是在调用者awaits调用方法时从它停止的地方继续.

换句话说--there is no thread,而async和await的"并发性"是由巧妙的控制流引起的幻觉,其细节隐藏在语法中.

现在,我是一名前汇编程序员,我非常熟悉指令指针、堆栈等.我了解正常的控制流(子程序、递归、循环、分支)是如何工作的.但是这些新的构造——我不明白.

当达到await时,运行时如何知道接下来应该执行哪段代码?它如何知道什么时候可以恢复到它停止的地方,又如何记得在哪里?当前的调用堆栈会发生什么情况,它会以某种方式保存吗?如果调用方法在await秒之前进行了其他方法调用——为什么堆栈没有被覆盖?在出现异常和堆栈展开的情况下,运行时究竟将如何处理所有这些问题?

当达到yield时,运行时如何跟踪应该拾取内容的点?迭代器状态是如何保持的?

推荐答案

我将在下面回答您的具体问题,但您最好还是简单阅读一下我关于如何设计Year和Wait的大量文章.

https://blogs.msdn.microsoft.com/ericlippert/tag/continuation-passing-style/

https://blogs.msdn.microsoft.com/ericlippert/tag/iterators/

https://blogs.msdn.microsoft.com/ericlippert/tag/async/

这些文章中有些已经过时了;生成的代码在很多方面都不同.但这些肯定会让你了解它是如何工作的.

此外,如果您不了解lambda是如何作为闭包类生成的,请理解first.如果你没有lambdas,你就不会有async的正面或反面.

当到达等待时,运行时如何知道接下来应该执行哪段代码?

await生成为:

if (the task is not completed)
  assign a delegate which executes the remainder of the method as the continuation of the task
  return to the caller
else
  execute the remainder of the method now

基本上就是这样.等待只是一种奇特的回报.

它如何知道什么时候可以恢复到它停止的地方,又如何记得在哪里?

那么,你是如何做到这一点的呢?当method foo调用method bar时,不知何故,我们记得如何回到foo的中间,不管bar做什么,foo激活的所有局部都完好无损.

你知道在汇编程序中是怎么做到的.foo的激活记录被推送到堆栈上;它包含了当地人的价值观.在调用时,foo中的返回地址被推送到堆栈上.当bar完成时,堆栈指针和指令指针将重置到它们需要的位置,而foo将从停止的位置继续运行.

等待的继续是完全相同的,不同之处在于记录被放到堆上的原因很明显,即the sequence of activations does not form a stack.

等待的委托作为任务的延续,包含(1)一个数字,该数字是查找表的输入,该表给出了下一步需要执行的指令指针,以及(2)局部变量和临时变量的所有值.

那里有一些额外的装备;例如,在.NET分支到try块的中间是非法的,因此不能简单地将try块中的代码地址粘贴到表中.但这些都是簿记细节.从概念上讲,激活记录只是移动到堆上.

当前调用堆栈会发生什么情况,会以某种方式保存吗?

当前激活记录中的相关信息从一开始就不会放在堆栈上;它从一开始就被分配到堆外.(通常,形式参数在堆栈或寄存器中传递,然后在方法开始时复制到堆位置.)

未存储呼叫者的激活记录;等待可能会回到他们身上,记住,所以他们会正常处理.

请注意,这是wait的简化延续传递样式与Scheme等语言中当前延续 struct 的true call之间的密切区别.在这些语言中,包括返回到调用方的延续在内的整个延续被call-cc捕获.

如果调用方法在等待之前进行了其他方法调用——为什么堆栈没有被覆盖?

这些方法调用返回,因此它们的激活记录在等待时不再在堆栈上.

在出现异常和堆栈展开的情况下,运行库究竟是如何完成这一切的呢?

在发生未捕获异常的情况下,会捕获异常,将其存储在任务中,并在获取任务结果时重新抛出.

还记得我之前提到的所有簿记吗?让我告诉你,正确的异常语义是一个巨大的痛苦.

当达到yield 率时,运行时如何跟踪应该提取内容的点?迭代器状态是如何保持的?

同样的方式.局部变量的状态被移动到堆中,一个数字表示MoveNext下次调用时应该恢复的指令,该数字与局部变量一起存储.

同样,迭代器挡路中有一堆齿轮,用于确保正确处理异常.

.net相关问答推荐

PowerShell - 如果用户输入凭据,则查询 AD 时出错

更改列表中的值

MassTransit RespondAsync 无法返回空值

如何知道变量是否只是指向另一个对象的pointer或者它是否可以独立存在

类似于字典但没有值的 C# 数据 struct

"投掷;" 是什么意思?靠自己做什么?

为什么这两个比较有不同的结果?

如何摆脱 VS2008 中的目标程序集不包含服务类型错误消息?

.Net 中的 Int128?

在 Moq Callback() 调用中设置变量值

ASP.NET Core 等效于 ASP.NET MVC 5 的 HttpException

.NET 如何判断路径是否是文件而不是目录?

双倍的? = 双倍? + 双倍?

C# 的浮点比较函数

清除文件内容

如何访问 Session 变量并在 javascript 中设置它们?

.NET 委托类型的正确命名约定?

在 C# 中使用 Bitmap 对象查找图像格式

有没有一种简单的方法来判断 .NET Framework 版本?

Linq Query 不断抛出无法创建 System.Object 类型的常量值......,为什么?