2012-04-24 77 views
3

我一直在玩弄下面的一段代码:ParameterizedThreadStart是否保证对象实例不会被垃圾收集?

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; 
    } 
} 

虽然主要在第一部分顺利执行,当我评论的调用Thread.Sleep()方法的第二部分失败,我得到一个空例外。

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

最终我不知道是否应该考虑尽可能多地使用ParameterizedThreadStart委托人,或者坚持使用lambda表达式,因为我尊重某些条件以保持其有效性。

回答

5

在我们谈论垃圾收集之前,先让我们了解您编写的代码。

之间有一个巨大的差异:即

new Thread(new ThreadStart(r.Start)) // identical to new Thread(r.Start) 
在任一上述的

new Thread(r.Start) 

其产生用于对r当前值Start方法的委托,r被评估现在,所以稍后更改为另一个实例(或null)不会影响它。与对比:

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

这与捕获可变r,即当匿名方法被调用,即第二线程运行时r只计算匿名方法。因此,是的:如果您更改r的值,您可能会得到不同的结果(如果将其更改为空值,则会发生错误)。

参数化的螺纹开始将工作太:

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

其中穿过的r作为参数值的当前,以至于现在是固定;当调用代理时,state在当时得到(以前)的值,因此您可以将其转换为适当的类型并安全地使用它。

Now!在垃圾收集方面,没有什么特别的要知道。是的,将参考r转换为ParameterizedThreadStart将创建引用的副本(不是对象的副本),因此将阻止垃圾收集,直到它不再处于范围内。然而,原来的方法也是如此。唯一一次变得棘手的是“捕获变量”的例子(() => r.Start()),尽管你看到的问题有没有什么关于GC,以及一切与捕获变量的规则有关。

2

很多我不明白的问题。我可以说的是,

Thread t = new Thread(r.Start) 

创建一个委托,它将内部指向提供方法实现的实例,通过委托调用。因此,将r设置为null将不起作用,因为存在线程 - > ParameterizedDelegate - >您的方法 - > RunMeBaby的关系

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

围绕r创建一个闭包,它仍然允许您修改r。如果您将其设置为空并稍后访问它,则会得到您的NRE