4

我一直在玩下面的代码:

class RunMeBaby
{
    public void Start()
    {
        while (true)
        {
            Console.WriteLine("I'm " + Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(1000);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        RunMeBaby r = new RunMeBaby();
        Thread t = new Thread(r.Start);  // ParameterizedThreadStart delegate
        r = null;
        GC.Collect(GC.MaxGeneration);
        t.Start();

        r = new RunMeBaby();
        t = new Thread(() => r.Start()); // ThreadStart delegate
        t.Start();
        //Thread.Sleep(1000);
        r = null;
    }
}

虽然 main 的第一部分执行顺利,但当我注释对Thread.Sleep()方法的调用时,第二部分失败,我得到一个空异常。

我的理解是 lambda 表达式被延迟评估,可能会发生新线程启动速度不够快并且主线程设置rnull第一个的情况。r现在我把这个“第二部分”放在一个具有本地范围的静态方法中,问题就消失了。但是我想知道在这种特殊情况下线程调度程序是否隐藏了问题,也许在具有不同工作负载的不同机器上仍然可能发生。或者关于 lambda 表达式是否有一些东西可以保证即使r超出范围,只要它没有被设置为null,它仍然以某种方式被引用。

最终我想知道我是否应该考虑ParameterizedThreadStart尽可能多地使用委托或坚持使用 lambda,因为我尊重某些条件以保持它们有效。

4

2 回答 2

6

在说垃圾回收之前,我们先来了解一下你写的代码。

两者之间存在巨大差异:

new Thread(r.Start)

Start它为当前值的方法创建一个委托r,即

new Thread(new ThreadStart(r.Start)) // identical to new Thread(r.Start)

在上述任何一种情况下,现在r都进行评估,因此以后对另一个实例(或 null)的更改不会影响它。对比:

new Thread(() => r.Start())

这是一个捕获变量的匿名方法r,即r在调用匿名方法时评估,即在第二个线程运行时。因此,是的:如果您更改 的值,r您很可能会得到不同的结果(如果将其更改为 null,则会出现错误)。

参数化线程启动也可以:

new Thread(state => ((RunMeBaby)state).Start(), r);

它将当前值作为参数值传递r,因此现在是固定的;调用委托时,state获取当时(以前)存在的值r,因此您可以将其转换为适当的类型并安全地使用它。

现在!在垃圾收集方面,没有什么特别要知道的。是的,将引用传递给raParameterizedThreadStart将创建引用的副本(而不是对象的副本),因此将阻止垃圾收集,直到它不再在范围内。然而,原始new Thread(r.Start)方法也是如此。唯一变得棘手的是“捕获变量”示例 ( () => r.Start()),尽管您看到的问题与 GC无关,而是与捕获变量的规则有关。

于 2012-04-24T09:07:32.523 回答
2

很多问题我都不明白。我能说的是

Thread t = new Thread(r.Start)

创建一个委托,该委托将在内部指向提供要通过委托调用的方法实现的实例。因此,将 r 设置为 null 将无效,因为存在Thread -> ParameterizedDelegate -> your method -> RunMeBaby的关系。

r = new RunMeBaby();
t = new Thread(() => r.Start());

在 r 周围创建一个闭包,它仍然允许您修改 r。如果您将其设置为 null 并稍后访问它,您将获得NRE

于 2012-04-24T09:07:00.963 回答