如何在.NET中产生并等待实现控制流? [英] How do yield and await implement flow of control in .NET?

查看:73
本文介绍了如何在.NET中产生并等待实现控制流?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

据我了解, yield 关键字,如果从迭代器块内部使用,它将控制流返回到调用代码,并且当再次调用迭代器时,它将

As I understand the yield keyword, if used from inside an iterator block, it returns flow of control to the calling code, and when the iterator is called again, it picks up where it left off.

此外,等待不仅等待被叫方,而且将控制权返回给调用方,只有在调用方唤醒方法时,才从中断处取回。

Also, await not only waits for the callee, but it returns control to the caller, only to pick up where it left off when the caller awaits the method.

换句话说- -没有线程,并且并发异步和等待的错觉是由控制流程的智能化引起的,其细节被语法隐藏了。

In other words-- there is no thread, and the "concurrency" of async and await is an illusion caused by clever flow of control, the details of which are concealed by the syntax.

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

Now, I'm a former assembly programmer and I'm very familiar with instruction pointers, stacks, etc. and I get how normal flows of control (subroutine, recursion, loops, branches) work. But these new constructs-- I don't get them.

当达到 await 时,运行时知道下一步应该执行什么代码?它如何知道何时可以从上次中断的地方恢复,以及如何记住在哪里?当前调用堆栈发生了什么,是否以某种方式保存了它?如果调用方法在 await s之前进行其他方法调用,怎么办?为什么堆栈不被覆盖?

When an await is reached, how does the runtime know what piece of code should execute next? How does it know when it can resume where it left off, and how does it remember where? What happens to the current call stack, does it get saved somehow? What if the calling method makes other method calls before it awaits-- why doesn't the stack get overwritten? And how on earth would the runtime work its way through all this in the case of an exception and a stack unwind?

yield 时,运行时如何处理所有这些情况?达到c $ c>时,运行时如何跟踪应该拾取的点?迭代器状态如何保存?

When yield is reached, how does the runtime keep track of the point where things should be picked up? How is iterator state preserved?

推荐答案

我将在下面回答您的特定问题,但是您只要阅读一下我的内容,可能会做得很好

I'll answer your specific questions below, but you would likely do well to simply read my extensive articles on how we designed yield and await.

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/

其中一些文章已经过时;生成的代码在很多方面都不同。

Some of these articles are out of date now; the code generated is different in a lot of ways. But these will certainly give you the idea of how it works.

此外,如果您不了解lambda如何作为闭包类生成,请理解 first 。如果没有lambda,则不会产生异步的结果。

Also, if you do not understand how lambdas are generated as closure classes, understand that first. You won't make heads or tails of async if you don't have lambdas down.


到达等待状态时,运行时知道下一步应该执行什么代码?

When an await is reached, how does the runtime know what piece of code should execute next?

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

基本上就是这样。


它如何知道何时可以从上次中断的地方恢复,以及如何记住在哪里?

How does it know when it can resume where it left off, and how does it remember where?

那么,您如何在没有等待的情况下做到?当foo方法调用方法bar时,无论如何做什么,我们都以某种方式记得如何回到foo的中间,所有激活foo的本地人都保持不变。

Well, how do you do that without await? When method foo calls method bar, somehow we remember how to get back to the middle of foo, with all the locals of the activation of foo intact, no matter what bar does.

您知道在汇编程序中是如何完成的。 foo的激活记录被压入堆栈;它包含本地人的值。在调用时,将foo中的返回地址压入堆栈。完成bar之后,堆栈指针和指令指针将重置为所需的位置,而foo将从其中断的位置继续。

You know how that's done in assembler. An activation record for foo is pushed onto the stack; it contains the values of the locals. At the point of the call the return address in foo is pushed onto the stack. When bar is done, the stack pointer and instruction pointer are reset to where they need to be and foo keeps going from where it left off.

await的继续是完全相同,除了记录由于明显的原因激活序列不形成堆栈而被放到堆上。

The continuation of an await is exactly the same, except that the record is put onto the heap for the obvious reason that the sequence of activations does not form a stack.

等待任务继续执行的委托包含(1)一个数字,该数字是查找表的输入,该表提供了您需要执行的指令指针接下来,以及(2)所有本地和临时值。

The delegate which await gives as the continuation to the task contains (1) a number which is the input to a lookup table that gives the instruction pointer that you need to execute next, and (2) all the values of locals and temporaries.

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

There is some additional gear in there; for instance, in .NET it is illegal to branch into the middle of a try block, so you can't simply stick the address of code inside a try block into the table. But these are bookkeeping details. Conceptually, the activation record is simply moved onto the heap.


当前调用堆栈发生了什么,是否以某种方式保存了它?

What happens to the current call stack, does it get saved somehow?

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

The relevant information in the current activation record is never put on the stack in the first place; it is allocated off the heap from the get-go. (Well, formal parameters are passed on the stack or in registers normally and then copied into a heap location when the method begins.)

调用者的激活记录不是储存

The activation records of the callers are not stored; the await is probably going to return to them, remember, so they'll be dealt with normally.

请注意,这是简化的继续传递样式之间的紧密区别。等待,以及您在诸如Scheme之类的语言中看到的真实的当前通话继续结构。在这些语言中, call-cc会捕获包括回叫者在内的整个延续。

Note that this is a germane difference between the simplified continuation passing style of await, and true call-with-current-continuation structures that you see in languages like Scheme. In those languages the entire continuation including the continuation back into the callers is captured by call-cc.


如果调用方法在等待之前进行其他方法调用怎么办?为什么堆栈不被覆盖?

What if the calling method makes other method calls before it awaits-- why doesn't the stack get overwritten?

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

Those method calls return, and so their activation records are no longer on the stack at the point of the await.


在异常和堆栈展开的情况下,运行时如何通过所有这些方式工作?

And how on earth would the runtime work its way through all this in the case of an exception and a stack unwind?

如果发生未捕获的异常,则捕获该异常并将其存储在任务中,并在获取任务结果时将其重新抛出。

In the event of an uncaught exception, the exception is caught, stored inside the task, and re-thrown when the task's result is fetched.

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

Remember all that bookkeeping I mentioned before? Getting exception semantics right was a huge pain, let me tell you.


当达到收益时,运行时如何跟踪该点应该在哪里捡东西?迭代器状态如何保留?

When yield is reached, how does the runtime keep track of the point where things should be picked up? How is iterator state preserved?

相同的方式。当地人的状态被移到堆上,一个数字表示 MoveNext 在下一次被调用时应继续执行的指令与本地人一起存储。

Same way. The state of locals is moved onto the heap, and a number representing the instruction at which MoveNext should resume the next time it is called is stored along with the locals.

同样,在迭代器块中有一堆齿轮可以确保正确处理异常。

And again, there's a bunch of gear in an iterator block to make sure that exceptions are handled correctly.

这篇关于如何在.NET中产生并等待实现控制流?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆