C# 析构函数永远不会被调用

C# 析构函数永远不会被调用,c#,multithreading,C#,Multithreading,我有一个类类,它在它的构造函数中创建一个线程。此线程运行一个while(true)循环,该循环正在从NetStream读取非关键数据。析构函数将中止线程: ~Class() { _thread.Abort(); _thread = null; } 当程序想要终止使用Class的实例-ClassInstance时,它调用: ClassInstance = null; GC.Collect; 我认为这意味着,~Class()将在此时自动成为调用者,但事实并非如此 即使在Application

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

~Class()
{
 _thread.Abort();
 _thread = null;
}
当程序想要终止使用
Class
的实例-
ClassInstance
时,它调用:

ClassInstance = null;
GC.Collect;
我认为这意味着,
~Class()
将在此时自动成为调用者,但事实并非如此


即使在
Application.Exit()
并从
Main()

返回后,此线程仍保持运行。如果实现
IDisposable
,并处置对象,则处置中的代码将运行,但不能保证也会调用析构函数

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


勾选此项

代码的关键部分未包含在内;线程是如何启动的以及它运行的方法。如果我不得不猜测的话,我会说很可能是您通过传递
Class
的实例方法启动了线程。因此,基本上,类实例仍然以线程的运行为根。您试图停止终结器中的线程,但终结器将永远不会运行,因为实例仍然是根实例,导致出现第22条军规的情况

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

使用TPL中包含的新机制。将
while(true)
循环改为轮询。在实施
IDisposable
时,在
Dispose
方法中发出取消信号。不要包含终结器(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.
    }
  }
}

CLR维护所有正在运行的线程。您将已将类的
InstanceMethod
作为
ThreadStart
ParameterizedThreadStart
委托传递给线程的构造函数
Delegate
将在
Target
属性中保存所传递方法的
MethodInfo
,以及类的
实例

垃圾收集器收集不应具有任何
强引用
但实例在
线程
委托
中仍处于活动状态的对象。因此,您的类仍然具有
强引用
,因此它不符合垃圾收集的条件

为了证明我上面所说的

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);
        }
    }
}
}


试试上面的代码<不会调用代码>析构函数
。要使其工作,请将
ThreadProc
方法标记为
static
,然后再次运行
Destructor
将被调用

稍微偏离主题:您可以使用裸线程代替裸线程来运行函数,而无需担心处理问题

这里有多个问题:

  • 将变量设置为null不会删除任何内容,它只是删除对实例的引用
  • 只有当垃圾收集器决定收集您的实例时,才会调用析构函数。垃圾收集器很少运行,通常只有在检测到内存压力时才会运行
  • 垃圾收集器仅收集孤立的集合。孤立表示对象指向的任何引用都无效
您应该实现IDisposable接口并调用Dispose方法中的任何清理代码。C#和VB使用关键字提供了
,即使在遇到异常的情况下也可以更轻松地进行处理

典型的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()。

C#class析构函数。使用用于资源的确定性清理。使用using()语句代替调用GC.Collect,一般建议您不要调用GC.Collect->将实例变量设置为
null
与调用析构函数不同!实际上,它对对象实例几乎没有影响,只减少了一个引用。因为它值得终结器在几乎所有琐碎的场景中运行
CriticalFinalizerObject
解决了一些问题,但仍有一些情况,尽管非常有限,终结器可能无法运行到完成。根据所提供的信息,我认为可以安全地假设这里没有这些场景。另一方面,调用
Thread.Abort
是一个可怕的想法。这就像是朝司机头部开了一枪,把车停了下来。汽车会停下来,但不知道在这个过程中会造成什么样的损坏。我最近在一个使用
BackgroundWorker
的GUI中切换了一些代码,以使用
TPL
,这给我留下了深刻的印象。