在下面的代码中,尽管TestMethod在'Messagebox'行之前肯定已经完成,为什么我们没有NullReference异常并且var2值为56
?
我从埃里克·利珀特(Eric Lippert)和this博客文章中读到this很好的答案,但我仍然不明白。
void TestMethod()
{
int var1 = 10;
List<long> list1 = new List<long>();
for (int i = 0; i < 5; i++)
list1.Add(i);
ThreadPool.QueueUserWorkItem(delegate
{
int var2 = var1;
Thread.Sleep(1000);
list1.Clear();
MessageBox.Show(var2.ToString());
});
var1 = 56;
}
答案 0 :(得分:3)
我认为这是因为委托人围绕变量var1
形成了闭包。大概研究内部封闭的工作方式将对您有所帮助。您可以参考说明here
编译器(与运行时相对)创建另一个类/类型。 您关闭的函数以及您关闭的任何变量 整个/吊起/捕获的代码将作为成员重写为整个代码 该类的。 .Net中的闭包被实现为 这个隐藏的类。
有了这个,我相信编译器生成的代码大致如下:
void TestMethod()
{
UnspeackableClosureClass closure = new UnspeackableClosureClass(10);
List<long> list1 = new List<long>();
for (int i = 0; i < 5; i++)
list1.Add(i);
ThreadPool.QueueUserWorkItem(closure.AutoGeneratedMethod);
closure.closureVar = 56;
}
public class UnspeackableClosureClass
{
public int closureVar;
public UnspeackableClosureClass(int val){closureVar=val}
public void AutoGeneratedMethod(){
int var2 = closureVar;
Thread.Sleep(1000);
list1.Clear();
MessageBox.Show(var2.ToString());
}
}
答案 1 :(得分:0)
我认为您的意思是希望var1
退出时TestMethod()
被取消分配。毕竟,局部变量存储在堆栈上,并且当方法退出时,堆栈指针必须还原到调用之前的位置,这意味着所有局部变量都已释放。如果确实是这样,var1
可能根本不会设置为null;它可能包含垃圾或某些其他局部变量的位,这些位稍后在堆栈指针再次移动时创建。那是你的意思吗?
对我来说,亮点是对异步思考根本不是基于堆栈的理解。堆栈只是不起作用-因为调用顺序不形成堆栈。取而代之的是,一些代码与保存在堆上的上下文对象相关联。它们可以按任何顺序执行,甚至可以同时执行。
您的委托人需要var1
,因此编译器将其从堆栈中保存的变量提升为与委托人的行为相关联的在这些对象之一中保存的变量。这就是所谓的“关闭”或“封闭变量”。对于委托人来说,它看起来就像一个局部变量,因为它不再位于堆栈中。只要该对象需要生存,它就会生存,即使在TestMethod()
退出之后也是如此。