我一直在玩下面这段代码:
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()
方法的调用进行注释时,第二部分失败了,我得到了一个null异常。
我的理解是lambda表达式被懒惰地评估,可能会发生新线程没有足够快地启动而主要线程首先将r
设置为null
。现在我将这个“第二部分”放在静态方法中,r
具有局部范围,问题就消失了。但是我想知道在特定情况下线程调度程序是否隐藏了该问题,可能在具有不同工作负载的不同机器上它仍然可能发生。或者有一些关于lambda表达式的东西可以保证即使r
超出范围,只要它没有被设置为null
,它仍然以某种方式被引用。
最后我想知道我是否应该考虑尽可能多地使用ParameterizedThreadStart
代表,或者坚持使用lambdas,因为我尊重某些条件以保证它们有效。
答案 0 :(得分:5)
在我们讨论垃圾收集之前,首先让我们理解您编写的代码。
之间存在巨大差异:
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
中 (之前)的值,因此您可以将其转换为适当的类型并安全地使用它。
现在!在垃圾收集方面,没有什么特别要知道的。是的,将引用r
传递给ParameterizedThreadStart
将创建引用的副本(不是对象的副本),因此将防止垃圾收集,直到它不再存在在适用范围。然而,原始的new Thread(r.Start)
方法也是如此。唯一一次变得棘手的是“捕获的变量”示例(() => r.Start()
),虽然您看到的问题
答案 1 :(得分:2)
很多我不明白的问题。我能说的是
Thread t = new Thread(r.Start)
创建一个委托,该委托将在内部指向提供通过委托调用的方法实现的实例。因此,将r设置为null将不起作用,因为存在 Thread - >的关系。 ParameterizedDelegate - >你的方法 - > RunMeBaby
r = new RunMeBaby();
t = new Thread(() => r.Start());
在r周围创建一个闭包,它仍然允许你修改r。如果您将其设置为null并稍后访问它,则会获得 NRE 。