Geo*_*uer -1 c# struct closures
我尝试了这段代码
struct Bar {
public int Value;
}
async Task doItLater(Action fn) {
await Task.Delay(100);
fn();
}
void Main() {
Bar bar = new Bar { Value = 1 }; //Bar is a struct
doItLater(() => {
Console.WriteLine(bar.Value);
}).Wait();
}
Run Code Online (Sandbox Code Playgroud)
得到了输出1.现在这让我很困惑.我的逻辑如下
Task.Delay(100)命中时,该执行线程完成,并且请求TPL fn()稍后执行.bar 存储在堆栈上,当我们在闭包中访问它时,该帧不应该存在.那么我怎么得到一个输出1?
彼得的回答是正确的; 加起来:
值类型不会"进入堆栈".当变量的生命周期很短时,变量会进入堆栈.变量的类型无关紧要; 当变量的生命周期不短时,包含int的变量会在堆上.如果知道其生命周期很短,则包含对字符串的引用的变量将进入堆栈.
await不会"终止线程".异步的await的全部意义在于,它并没有要求另一个线程!异步等待的重点是在等待异步操作完成时继续使用当前线程.如果您认为await与线程有任何关系,请阅读"没有线程".它没有.
但我想解决关于堆栈的基本错误,作为延续的具体化.
什么是延续?这只是一个奇特的词汇"在这一点上,这个程序接下来要做什么?"
在正常的代码中 - 没有等待,没有收益,没有lambda,没有什么花哨 - 事情非常简单.当你有:
y = 123;
x = f();
g(x);
return y;
Run Code Online (Sandbox Code Playgroud)
f的延续是"为x分配值并将其传递给g",g的延续是"运行我现在正在使用的任何方法的延续,赋予它y的值".
如您所知,我们可以使用堆栈在正常程序中重新启用continuation.堆栈数据结构实际上维护了三件事:
但是这个数据结构只是一个堆栈,因为函数激活在逻辑程序中逻辑上形成堆栈.函数Foo调用Bar,然后Bar调用Blah,然后Blah返回Bar,Bar返回Foo.
现在让我们皱起眉头:
int y = 123;
Func<int> f = () => y;
return f;
Run Code Online (Sandbox Code Playgroud)
现在,即使在当前方法返回之后,也必须保留局部y的值,因为可能会调用委托.因此,y的生命周期长于当前激活的寿命,并且不会进入堆栈.
或这个:
int y = 123;
yield return y;
yield return y;
Run Code Online (Sandbox Code Playgroud)
现在必须在迭代器块的MoveNext的调用中保留y ,所以同样,y必须是不在堆栈上的变量; 它的使用寿命很长,所以它必须在堆上.但请注意,这比前一种情况更奇怪,因为该方法的激活可以通过yield暂停,并由未来的MoveNext恢复.现在我们有一种情况,方法调用不会在逻辑上形成堆栈,因此连续信息也不能再进入堆栈.
等待是一样的; 我们还有一个案例,一个方法可以暂停,其他东西可以在同一个线程上发生,然后以某种方式该方法从它停止的地方恢复.
Await和yield都是一个称为coroutines的更通用的功能的例子.普通方法可以做三件事:抛出,挂起或返回.一个协程可以做第四件事:暂停,以后再恢复.
正常方法只是协同程序的一个特例; 常规方法是不挂起的协同程序.由于它们具有此限制,因此普通方法可以使用堆栈作为其延续语义的具体化. 协同程序没有.由于协同程序的激活不形成堆栈,因此堆栈不用于它们的本地变量激活记录; 也不用于存储返回地址等延续信息.
你已经陷入了相信特殊情况 - 不停止的惯例 - 是世界必须如何的陷阱,但事实并非如此.相反,非挂起方法是可以通过使用堆栈来存储其连续信息来优化的特殊方法.