使用延迟执行保留超出范围的堆栈变量

时间:2013-10-24 22:14:21

标签: c# .net multithreading stack

我承认这个问题显示了很多关于堆栈和堆栈的误解。希望我在这里提出一个恰当的问题。

在C#中,如何为代码处理堆栈帧和局部变量,如下所示。让这些案例变得有趣的是,StartTasks中的变量j和StartTasks2中的变量i都被Task使用,这些变量可能在这些变量不再在范围内之后运行,并且在堆栈帧之后运行它们通常会被弹出堆。

此外,在什么条件下,像j这样不断被“重新创建”的局部变量在超出范围后会获得一个全新的内存槽,就像在StartTask中发生的那样,该局部变量存在于何处(即StartTasks的堆栈帧,这意味着框架不能被移除,或其他地方)?

void StartTasks() {
    int i = 0;
    while ( i < 10000 ) {
        int j = i;
        Task.Run( () => ExecuteThis( j ) ); // eac
    }
}

void StartTasks2() {
    int i = 0;
    while ( i < 10000 ) {
        Task.Run( () => ExecuteThis( i ) ); // eac
    }
}


void BigBoss() {
    StartTasks();
    StartTasks2();
    NowMakeMoreCalls();
}

2 个答案:

答案 0 :(得分:5)

它们被提升为编译器生成的类的一部分。

对您提供的内容进行简单反编译可以得到一些答案:

[CompilerGenerated]
private sealed class <>c__DisplayClass5
{
  public int i;
  public Program <>__this;

  public <>__DisplayClass5()
  {
    base.<>ctor();
  }

  public void <CStartTasks2>b__3()
  {
    this.<>4__this.ExecuteThis(this.i);
  }
}

编译器生成此类并将对该调用类的引用传递给它。它还将关闭的变量存储为实例字段。

所以,回答这个问题......他们没有在堆栈上分配。它们形成在编译时定义的对象,并在运行时实例化。

答案 1 :(得分:3)

  

这个问题显示了很多关于堆栈和堆栈的误解。

你的理解是好的,但你不是把所有的事实放在一起来达成解释。你有90%的路要走。

堆栈帧是一个实现细节。不要求将本地实现为堆栈槽。请记住,本地 local 的原因并不在于它在堆栈上。它们被称为本地变量,而不是堆栈变量。什么使本地成为本地是因为它的名称只在方法中有意义。

  

使这些案例变得有趣的是,StartTasks中的变量j和StartTasks2中的变量i都被Task使用,这些变量可能在这些变量不再在范围内之后运行,并且在堆栈帧之后它们通常运行弹出堆栈。

首先,您错误地使用了术语“范围”。 “范围”是编译时概念;局部变量的范围是代码文本的区域,可以通过其名称访问该变量。您正在使用“范围”作为堆栈帧生命周期的运行时概念。那不是范围;这是一生。

您应该注意,局部变量的生命周期比堆栈帧的生命周期长。显然,这意味着本地无法实现为堆栈槽。事实并非如此。那个地方被认为是一个领域。该字段是任务引用的对象的字段。

  

在什么条件下,像j这样不断被“重新创建”的局部变量在超出范围后获得一个全新的内存插槽

重新使用插槽会产生错误的程序!重新使用插槽是一种优化。编译器不会进行创建错误程序的优化。

  

该局部变量在哪里生效?

如果已知变量的生命周期与堆栈帧的生命周期相同(或更少),则它可以进入堆栈。如果没有那么它必须在堆上。