析构函数永远不会被调用

时间:2013-08-28 13:59:43

标签: c# multithreading

我有一个类Class,在其构造函数中创建Thread。该线程运行while(true)循环,从NetStream读取非关键数据。线程将被析构函数中止:

~Class()
{
 _thread.Abort();
 _thread = null;
}

当程序想要结束使用Class的实例 - ClassInstance时,它会调用:

ClassInstance = null;
GC.Collect;

我认为这意味着~Class()会在那时自动调用 - 但事实并非如此。

此线程即使在Application.Exit()之后仍然继续运行,并从Main()返回。

4 个答案:

答案 0 :(得分:8)

不包括代码的关键部分;线程如何启动以及它运行的方法。如果我不得不猜测,我会说你可能通过传递Class的实例方法来启动线程。所以基本上你的类实例仍然以线程的运行为根。您尝试在终结器中停止线程,但终结器将永远不会运行,因为实例仍然是root,导致catch-22情况。

另外,您提到线程正在运行非关键代码,这是您使用Thread.Abort的理由。这真的不是一个足够好的理由。很难控制将ThreadAbortException注入线程的位置,因此它可能会破坏您没有预料到的关键程序数据结构。

使用TPL附带的新cooperative cancellation机制。更改while (true)循环以轮询CancellationToken。实施Dispose时,在IDisposable方法中发出取消信号。不要包含终结器(C#术语中的析构函数)。终结器旨在用于清理非托管资源。由于您没有表明非托管资源正在发挥作用,因此拥有终结器毫无意义。在实施IDisposable时,您不必包含终结器。事实上,如果没有真正需要它,那么被认为是不好的做法。

public class Class : IDisposable
{
  private Task task;
  private CancellationTokenSource cts = new CancellationTokenSource();

  Class()
  {
    task = new Task(Run, cts.Token, TaskCreationOptions.LongRunning);
    task.Start();
  }

  public void Dispose()
  {
    cts.Cancel();
  }

  private void Run()
  {
    while (!cts.Token.IsCancellationRequested)
    {
      // Your stuff goes here.
    }
  }
}

答案 1 :(得分:6)

如果你实现了IDisposable并处置了对象,那么Dispose中的代码将会运行,但是不能保证也会调用析构函数。

垃圾收集者认为这是浪费时间。因此,如果您想要一个可预测的处置,您可以使用IDisposable

选中此Thread

答案 2 :(得分:2)

CLR维护所有正在运行的线程。您将以InstanceMethodThreadStart委托的身份将类的ParameterizedThreadStart传递给线程的构造函数。 Delegate将保留您传递的方法的MethodInfo以及Instance属性中您班级的Target

垃圾收集器收集和对象不应该有Strong References,但您的实例在Delegate Thread内仍然存在。因此,您的班级仍然拥有Strong Reference,因此不符合垃圾回收的条件。

证明我上面所说的

public class Program
{
    [STAThread]
    static void Main(string[] args)
    {
        GcTest();

        Console.Read();
    }

    private static void GcTest()
    {
        Class cls = new Class();
        Thread.Sleep(10);
        cls = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

public class Class
{
    private Thread _thread;
    ~Class()
    {
        Console.WriteLine("~Class");
        _thread.Abort();
        _thread = null;
    }

    public Class()
    {
        _thread = new Thread(ThreadProc);
        _thread.Start();
    }

    private void ThreadProc()
    {
        while (true)
        {
            Thread.Sleep(10);
        }
    }
}

}

试试上面的代码。 Destructor不会被调用。要使其正常工作,请将ThreadProc方法标记为static并再次运行Destructor 将被称为

答案 3 :(得分:2)

稍微偏离主题:您可以使用Tasks而不是裸线来运行功能而无需担心丢弃。

这里有多个问题:

  • 将变量设置为null不会删除任何内容,只会删除对实例的引用。
  • 只有在垃圾收集器决定收集您的实例时才会调用析构函数。垃圾收集器很少运行,通常只有在检测到存在内存压力时才会运行。
  • 垃圾收集器仅收集孤立的集合。孤立意味着您的对象指向的任何引用都是无效的。

您应该实现IDisposable接口并在Dispose方法中调用任何清理代码。 C#和VB提供using关键字,即使面对异常也可以轻松处理。

典型的IDisposable实现类似于以下内容:

class MyClass:IDisposable
{
    ClassB _otherClass;

    ...


    ~MyClass()
    {
         //Call Dispose from constructor
         Dispose(false);
    }

    public void Dispose()
    {
        //Call Dispose Explicitly
        Dispose(true);
        //Tell the GC not call our destructor, we already cleaned the object ourselves
        GC.SuppressFinalize(this);
    }

    protected virtual Dispose(bool disposing)
    {
        if (disposing)
        {
            //Clean up MANAGED resources here. These are guaranteed to be INvalid if 
            //Dispose gets called by the constructor

            //Clean this if it is an IDisposable
            _otherClass.Dispose();

           //Make sure to release our reference
            _otherClass=null;
        }
        //Clean UNMANAGED resources here
    }
}

然后您可以像这样使用您的课程:

using(var myClass=new MyClass())
{
    ...
}

一旦using块终止,即使发生异常,也会调用Dispose()。