我有一个类Class
,在其构造函数中创建Thread
。该线程运行while(true)
循环,从NetStream
读取非关键数据。线程将被析构函数中止:
~Class()
{
_thread.Abort();
_thread = null;
}
当程序想要结束使用Class
的实例 - ClassInstance
时,它会调用:
ClassInstance = null;
GC.Collect;
我认为这意味着~Class()
会在那时自动调用 - 但事实并非如此。
此线程即使在Application.Exit()
之后仍然继续运行,并从Main()
返回。
答案 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维护所有正在运行的线程。您将以InstanceMethod
或ThreadStart
委托的身份将类的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而不是裸线来运行功能而无需担心丢弃。
这里有多个问题:
您应该实现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()。