线程防止所有者的垃圾收集

时间:2013-03-29 01:33:26

标签: c# multithreading memory-leaks garbage-collection

在我创建的库中,我有一个类DataPort,它实现类似于.NET SerialPort类的功能。它与某些硬件进行通信,并且只要数据通过该硬件进入就会引发事件。为了实现此行为,DataPort会旋转一个预期与DataPort对象具有相同生命周期的线程。 问题是当DataPort超出范围时,它永远不会被垃圾收集

现在,由于DataPort与硬件(使用pInvoke)进行通信并拥有一些非托管资源,因此它实现了IDisposable。当您在对象上调用Dispose时,一切都正常。 DataPort摆脱了所有非托管资源并杀死了工作线程并消失了。但是,如果只是让DataPort超出范围,垃圾收集器将永远不会调用终结器,并且DataPort将永远保留在内存中。我知道这种情况有两个原因:

  1. 终结器中的断点永远不会被击中
  2. SOS.dll告诉我DataPort还活着
  3. 边栏:在我们继续前进之前,我会说是的,我知道答案是" Call Dispose()Dummy!"但我认为,即使你让所有引用超出范围,正确的事情应该发生最终并且垃圾收集器应该摆脱DataPort

    回到问题:使用SOS.dll,我可以看到我的DataPort没有被垃圾收集的原因是它旋转的线程仍然有一个引用DataPort对象 - 通过隐式" this"线程正在运行的实例方法的参数。正在运行的工作线程will not be garbage collected,因此在正在运行的工作线程范围内的任何引用也不符合垃圾回收的条件。

    线程本身基本上运行以下代码:

    public void WorkerThreadMethod(object unused)
    {
      ManualResetEvent dataReady = pInvoke_SubcribeToEvent(this.nativeHardwareHandle);
      for(;;)
      {
        //Wait here until we have data, or we got a signal to terminate the thread because we're being disposed
        int signalIndex = WaitHandle.WaitAny(new WaitHandle[] {this.dataReady, this.closeSignal});
        if(signalIndex == 1) //closeSignal is at index 1
        {
          //We got the close signal.  We're being disposed!
          return; //This will stop the thread
        }
        else
        {
          //Must've been the dataReady signal from the hardware and not the close signal.
          this.ProcessDataFromHardware();
          dataReady.Reset()
        }
      }
    }
    

    Dispose方法包含以下(相关)代码:

    public void Dispose()
    {
      closeSignal.Set();
      workerThread.Join();
    }
    

    因为线程是gc根并且它拥有对DataPort的引用,所以DataPort永远不会有资格进行垃圾回收。因为永远不会调用终结器,所以我们永远不会向工作线程发送关闭信号。因为工作线程永远不会得到关闭信号,所以它会一直持续并保持该引用。 ACK!

    我能想到这个问题的唯一答案就是摆脱这个'这个问题。 WorkerThread方法的参数(详见下面的答案)。任何人都可以想到另一种选择吗?必须有一种更好的方法来创建一个具有与对象相同生命周期的线程的对象!或者,这可以在没有单独的线程的情况下完成吗?我在msdn论坛上选择了基于this post的特定设计,描述了常规.NET串口类的一些内部实现细节

    更新评论中的一些额外信息:

    • 相关主题将IsBackground设置为true
    • 上述非托管资源不会影响问题。即使示例中的所有内容都使用了托管资源,我仍然会看到相同的问题

1 个答案:

答案 0 :(得分:0)

我认为问题不在你所展示的代码中,而是在使用此串口包装类的代码中。如果那里没有“使用”语句,请参阅http://msdn.microsoft.com/en-us/library/yh598w02.aspx,您没有确定性清理行为。相反,你依赖垃圾收集器但是永远不会收到仍被引用的对象,并且线程的所有堆栈变量(无论是普通参数还是this-pointer)都算作引用。