18

我有一个在它的构造函数Class中创建的类。Thread该线程运行一个while(true)循环,从NetStream. 线程将被析构函数中止:

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

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

ClassInstance = null;
GC.Collect;

我认为这意味着那时~Class()它将自动成为呼叫者 - 但事实并非如此。

该线程甚至在Application.Exit()Main().

4

4 回答 4

9

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

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

使用TPL 中包含的新的合作取消机制。更改while (true)循环以轮询CancellationTokenDispose实现时在方法中发出取消信号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.
    }
  }
}
于 2013-08-28T14:04:45.147 回答
7

如果您实现IDisposable并释放对象,则 Dispose 中的代码将运行,但不能保证也会调用 Destructor。

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

检查这个线程

于 2013-08-28T14:03:33.630 回答
2

CLR 维护所有正在运行的线程。您将已将InstanceMethod您的类的 传递给线程的构造函数作为任一ThreadStartParameterizedThreadStart委托。Delegate将保存MethodInfo您传递的方法和属性Instance中的类。Target

垃圾收集器收集不应该有的对象,Strong References但您的实例仍然存在DelegateThread. 所以你的班级仍然有,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 将被调用

于 2013-08-28T14:11:53.883 回答
2

稍微偏离主题:您可以使用任务而不是裸线程来运行函数,而无需担心处置。

这里有多个问题:

  • 将变量设置为 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()。

于 2013-08-28T14:14:46.250 回答