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实现类似于以下内容:
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
,这给我留下了深刻的印象。