Деструктор никогда не вызван
У меня есть класс Class
, который создает конструктор Thread
в нем. В этом потоке выполняется цикл while(true)
, который считывает некритические данные из NetStream
. Нить будет прервана деструктором:
~Class()
{
_thread.Abort();
_thread = null;
}
Когда программа хочет закончить использование экземпляра Class
- ClassInstance
, он вызывает:
ClassInstance = null;
GC.Collect;
Я думал, что это означает, что ~Class()
автоматически будет вызывающим в этой точке, но это не так.
Этот поток продолжает работать даже после Application.Exit()
и возвращается из Main()
.
Ответы
Ответ 1
Ключевой бит вашего кода не включен; как запускается поток и какой метод он запускает. Если бы я должен был предположить, я бы сказал, что, вероятно, вы начали поток, передав метод экземпляра Class
. Таким образом, в основном ваш экземпляр класса по-прежнему коренится в процессе работы потока. Вы пытаетесь остановить поток в финализаторе, но финализатор никогда не будет запущен, потому что экземпляр все еще внедрен, что приводит к ситуации catch-22.
Кроме того, вы упомянули, что поток работает с некритическим кодом, и это было вашим оправданием для использования Thread.Abort
. Это действительно не очень хорошая причина. Очень сложно контролировать, где ThreadAbortException
будет впрыскиваться в поток, и в результате он может повредить критические структуры данных программы, которые вы не ожидали.
Используйте новые механизмы сотрудничества с отменой, включенные в TPL. Измените цикл while (true)
, чтобы опросить CancellationToken. Сигнал отмены в методе Dispose
при реализации IDisposable
. Не включайте финализатор (деструктор в терминологию С#). Финализаторы предназначены для очистки неуправляемых ресурсов. Поскольку вы не указали, что неуправляемые ресурсы находятся в игре, то бессмысленно иметь финализатор. При реализации 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.
}
}
}
Ответ 2
Если вы реализуете IDisposable
и удаляете объект, тогда будет запущен код в Dispose, но нет гарантии, что Destructor также будет вызван.
Сборщик мусора выражает мнение, что это пустая трата времени. Поэтому, если вы хотите иметь предсказуемое распоряжение, вы можете использовать IDisposable
.
Отметьте Thread
Ответ 3
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
, который будет называться
Ответ 4
Немного не по теме: вы можете использовать Tasks вместо голых потоков для запуска функций, не беспокоясь об утилизации.
Здесь есть несколько проблем:
- Установка переменной в значение null не удаляет ничего, она просто удаляет ссылку на ваш экземпляр.
- Деструктор вызывается только тогда, когда сборщик мусора решает собрать ваш экземпляр. Сборщик мусора работает нечасто, как правило, только тогда, когда он обнаруживает, что есть давление в памяти.
- Сборщик мусора собирает ТОЛЬКО сиротские коллекции. Сиротство означает, что любые ссылки, на которые указывает ваш объект, являются недействительными.
Вы должны реализовать интерфейс IDisposable и вызвать любой код очистки в методе Dispose. С# и 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() будет вызываться, даже если возникает исключение.