【问题标题】:Passing a local variable to another thread in .net将局部变量传递给.net中的另一个线程
【发布时间】:2018-09-29 07:42:24
【问题描述】:

在下面的代码中,为什么我们没有得到 NullReference 异常并且 var2 的值是 56 虽然 TestMethod 肯定在 'Messagebox' 行之前完成了? 我阅读了来自 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;
}

【问题讨论】:

  • 您链接到我的答案是关于 C,而不是 C#。托管内存语言不存在在其生命周期结束后访问变量的问题,因为内存是托管,因此这永远不会发生。
  • 您不一定会在新线程中得到 var2 为 56。你写了一个竞态条件;线程 race 来确定谁首先获得分配,并且您无法保证哪个线程将获胜。
  • 你说你期望一个空解引用,但是你认为哪个引用是空的?您的程序中没有空引用!
  • @EricLippert,关于竞争条件,我明白了你的意思,但是如果我们重新排序前两行(int var2 = var1Thread.Sleep)并让调用者方法完成它的工作,我们仍然得到相同的结果。也许这里有一些我不知道的与实现相关的东西。
  • @EricLippert,关于空引用,我预计当TestMethod 方法完成时,它的堆栈被删除,所以list1 变量。

标签: c# .net multithreading closures local-variables


【解决方案1】:

我认为这是因为delegate 已经围绕变量var1 形成了闭包。可能看看闭包在内部是如何工作的会对你有所帮助。可以参考解释here

编译器(相对于运行时)创建另一个类/类型。 带有您的闭包的函数和您关闭的任何变量 over/hoisted/captured 在你的代码中被重写为成员 那个班的。 .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());
  }
}

【讨论】:

  • 您的猜测接近现实。对于实际生成的源,请查看sharplab.io
【解决方案2】:

我认为您的意思是当TestMethod() 退出时,您希望var1释放。毕竟,局部变量存储在堆栈中,当方法退出时,堆栈指针必须恢复到调用之前的位置,这意味着所有局部变量都被释放。如果这真的发生了,var1 可能根本不会设置为 null;它可能包含垃圾,或其他一些局部变量的位,这些位是稍后在堆栈指针再次移动时创建的。是这个意思吗?

让我大开眼界的是异步思维根本不是基于堆栈的理解。堆栈不起作用——因为调用的顺序不会形成堆栈。相反,代码位与保存在 heap 上的上下文对象相关联。它们可以按任何顺序执行,甚至可以同时执行。

您的委托需要var1,因此编译器将其从堆栈中保存的变量提升为这些对象之一中保存的变量,与委托的行为相关联。这就是所谓的“闭包”或“封闭变量”。对于委托人来说,它看起来就像一个局部变量,因为它——只是不再在堆栈上。只要该对象需要存在,它就会存在,即使在 TestMethod() 退出之后也是如此。

【讨论】:

  • 你是这个意思吗?是的。确切地。现在我对这种行为有了更好的理解。
  • 正确的思考方式是停止认为“本地意味着堆栈”。 生命周期短意味着堆栈。 “本地”是事物名称的属性; “local”表示这个变量的名字只在变量的locality中才有意义。此外,不要将堆栈视为“堆栈”。将其视为短期分配池。现在我们正确的表征是什么? 短期分配池中存在短期变量。这是显然正确,这是我们想要的解释。
  • 由于匿名函数中使用的局部变量具有与委托相同的生命周期,并且由于委托可能长期存在,我们不能将本地人放在 短期池 中,所以我们不这样做。我们将它放在长期分配池中,直到垃圾收集器释放它为止。
猜你喜欢
  • 2011-04-27
  • 2015-03-10
  • 1970-01-01
  • 2014-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-07
相关资源
最近更新 更多