包含有无限循环的线程的对象的垃圾收集

本文关键字:线程 无限循环 包含 对象 | 更新日期: 2023-09-27 18:15:19

假设我有一个类,它做的事情是:

 public class Foo
 {
     private bool _forceStop = false;
     private Queue<object> queue;
     private void ProcessInBackground()
     {
          while(!forceStop )
          {
              Moniter.Enter(queue);
              while(!_forceStop && queue.Count == 0)Moniter.Wait(queue);
              object data = null;                 
              if (!_forceStop)
                 data = queue.Dequeue(); 
              Moniter.Exit(queue);
              if (data != null)
                 processData(data);
           }
      }
      ...
  }

如果类Foo的对象不再被使用,并且_forceStop从未在该对象中设置为true,并且假设ProcessInBackground已被调用,它将被收集吗?

编辑:已解决,模糊性增加了线程安全性。抱歉,当我写这个例子时,我只是虚构了一个场景

包含有无限循环的线程的对象的垃圾收集

(像Marc一样,我假设您指的是您称之为ProcessInBackground的对象。我还假设queue是一个字段。)

不-你仍然指的是queue,这意味着对象中的字段将被读取,这意味着包含对象不能被垃圾收集。

请注意,仅仅因为方法在对象中运行,并不会阻止垃圾收集——垃圾收集器只关心是否有可能使用引用本身,或者是否有可能读取对象中的字段。演示代码:

using System;
using System.Threading;
public class OddFinalizer   
{
    int field;
    public OddFinalizer(int field)
    {
        this.field = field;
    }
    ~OddFinalizer()
    {
        Console.WriteLine("OddFinalizer");
    }
    public void Work()
    {
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine("In loop before last access...");
            GC.Collect();
            GC.WaitForPendingFinalizers();            
            Thread.Sleep(1000);
        }
        Console.WriteLine("Field value: {0}", field);
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine("In loop after last access...");
            GC.Collect();
            GC.WaitForPendingFinalizers();
            Thread.Sleep(1000);
        }
    }
    static void Main(string[] args)
    {
        new OddFinalizer(10).Work();
    }
}

结果(使用/o+编译,不是在调试器中运行):

In loop before last access..
In loop before last access..
In loop before last access..
In loop before last access..
In loop before last access..
Field value: 10
In loop after last access...
OddFinalizer
In loop after last access...
In loop after last access...
In loop after last access...
In loop after last access...

是的,将收集object data(假设processData()没有将其放入列表中)。GC可以处理(托管)线程,如果不能,世界将会结束。

但是你的代码是不是线程安全的,你的DeQueueing在你放弃锁之后。

改进建议:

//untested
private bool _forceStop = false;
private object _locker = new object();  // better not depend on queue here
private void ProcessInBackground()
{
    while(true)
    {
       // Moniter.Enter(queue);
       lock(_locker)
       {       
          while(!_forceStop && queue.Count == 0)
            Moniter.Wait(_locker);
          //Moniter.Exit(queue);
          if (_forceStop) break;
          object data = queue.Dequeue(); 
       }
       processData(data);
    }
}
编辑:

在第二次读取时,这是关于包含对象的。这当然不会被线程收集。

假设"对象"是指拥有该方法的实例,那么如果线程正在执行ProcessInBackground方法,则该实例由线程根(它是arg0,并且您在循环中使用它的字段)。所以不,它不会被收集。

如果"对象"指的是"数据",那么除了最近的对象外,所有对象都当然符合条件。最近的可能取决于编译器配置(它是否消除了本地?)和CLI(它是否检查未读取的本地?)我会说:"它可能是合格的,在发布/优化;

和_forceStop从未设置为true,对象会被收集吗?

当一个对象的方法处于线程的调用栈中时,该对象不能被垃圾收集。调用栈有对对象的引用。

在这个例子中,ProcessInBackground是一个线程调用栈上的方法。


Jon的回答纠正了我的答案——垃圾收集器在确信没有更多的引用将被使用时工作,包括this引用。这意味着,当一个对象在调用堆栈上有一个方法时,它可以被收集(这个方法可以使用其他方法,但不使用这个实例自己的任何成员)。

在我自己的代码中,没有终结器。当它们被收集起来的时候,我很少在乎它们。当我期望收集这些时,它们不在调用堆栈上。如果它们在调用堆栈上被收集,因为。net认为这是一件好事,对我来说没有问题。

我不认为这个细节会改变我应该如何写任何代码,所以我会选择继续写代码,就像我上面错误的事实是真的一样,同时稍微注意到它的错误。如果我的情况和你的相似,你也可以这样做。