线程如何在方法完成后访问本地变量

本文关键字:访问 变量 方法 线程 | 更新日期: 2023-09-27 18:21:15

假设我有一个类似的C#方法

public void MyMethod()
    {
        int i = 0;
        var thread = new Thread(() =>
        {
            Thread.Sleep(100);
            if (i == 0)
            {
                Console.WriteLine("Value not changed and is {0}", i);
            }
            else
            {
                Console.WriteLine(" Value changed to {0}.", i);
            }
        });
        thread.Start();

        i = 1;
    }

方法创建一个线程,该线程访问方法中创建的局部变量。当它访问这个变量时,该方法已经完成,因此局部变量i应该不存在。但是代码运行起来没有任何问题。根据我的理解,在方法块完成后,局部变量不存在。我不能得到这个。

线程如何在方法完成后访问本地变量

这之所以有效,是因为编译器会重写代码以使用闭包。

由于在lambda中使用该变量,因此该变量最终会被更改为类的成员。编译后的代码不包含i的局部变量,即使您是这样写的。相反,它重写代码以使用编译器生成的类,该类包含Int32作为成员变量,而"本地代码"和lambda则引用该类成员。

有关详细信息,请参阅这篇关于闭包的博客文章,它让您大致了解编译器在这里的作用。

根据我的理解,在方法块完成后,局部变量就不存在了。我不能得到这个。

你的理解完全是错误的。局部变量的定义特征是其名称仅在其声明块内的范围内。这就是为什么它被称为"局部"变量的原因——因为它的名称只在局部可见。

你相信"本地"意味着"短命"的谎言。这是不正确的;局部变量在可能的情况下是短暂的,但在三种情况下它是不可能的:当它是匿名函数的封闭外部变量时,当它在迭代器块中时,或当它在异步块中时。

无独有偶,你的问题是我上周博客的主题;查看更多详细信息:

http://blogs.msdn.com/b/ericlippert/archive/2012/01/16/what-is-the-defining-characteristic-of-a-local-variable.aspx

这被称为闭包
它将局部变量扩展到方法的生存期之外。

作为新线程启动的lambda表达式在i上创建一个闭包;由于闭包关闭的是变量而不是值,因此线程引用与外部作用域相同的对象,并且即使在外部作用域结束后也能够访问其变量。

C#编译器通常将C#代码转换为一种称为MSIL的中间"语言",该语言与C#一样具有局部变量。MSIL中的局部变量的行为与您期望的C#变量的行为一样:当定义它们的例程退出时,它们就不存在了。此外,当使用局部变量的C#代码被转换为使用局部变量时,这些C#变量的行为与MSIL变量类似:当定义函数退出时,它们就不存在了。然而,并不是所有使用局部变量的C#代码都使用MSIL局部变量来存储它们。

在各种情况下,编译器将使用为使用局部变量而编写的代码,在将其转换为MSIL之前,对其进行重写,以便定义一个新的类对象,该对象包含所讨论变量的字段,然后重写对这些变量的访问,以便它们访问新字段。如果"变量"由委托使用,则该委托将获得对该新类对象的引用。即使定义对象的函数退出,只要任何东西(包括委托的任何副本)都有引用,对象本身就会继续存在。

确定编译器何时使用MSIL局部变量以及何时重写以使用类字段的规则可能很棘手,对例程的一部分进行微小更改可能会更改其他看似不相关的部分的语义。