ParameterizedThreadStart是否保证对象实例不会被垃圾回收?

To마*_*마SE 4 c# lambda delegates thread-safety

我一直在玩下面这段代码:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然main的第一部分是顺利执行的,但是当我对Thread.Sleep()方法的调用发表评论时,第二部分失败,我得到一个null异常.

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

最后我想知道我是否应该考虑ParameterizedThreadStart尽可能多地使用代表或坚持lambdas,因为我尊重某些条件以保持它们的有效性.

Mar*_*ell 6

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

两者之间存在巨大差异:

new Thread(r.Start)
Run Code Online (Sandbox Code Playgroud)

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

new Thread(new ThreadStart(r.Start)) // identical to new Thread(r.Start)
Run Code Online (Sandbox Code Playgroud)

在任一上述情况,r进行评价现在,所以后来改变到另一个实例(或空)将不会影响它.对比:

new Thread(() => r.Start())
Run Code Online (Sandbox Code Playgroud)

这是一个捕获变量的匿名方法r,即r在调用匿名方法时,即第二个线程正在运行时进行评估.因此,是的:如果你改变了你的值,r你很可能得到一个不同的结果(如果你把它改成null,则会出错).

参数化线程启动也会起作用:

new Thread(state => ((RunMeBaby)state).Start(), r);
Run Code Online (Sandbox Code Playgroud)

它将当前r作为参数值传递,以便现在修复 ; 当调用委托时,state获取当时(之前)的值r,因此您可以将其强制转换为适当的类型并安全地使用它.

现在!在垃圾收集方面,没有什么特别要知道的.是的,通过基准rParameterizedThreadStart将创建一个副本的引用(而不是对象的副本),这样可以防止垃圾收集,直到它不再是在范围内.然而,原始new Thread(r.Start)方法也是如此.唯一一次变得棘手的是"捕获变量"示例(() => r.Start()),尽管您看到的问题与GC 没有任何关系,而且与捕获变量的规则有关.