Подтвердить что ты не робот

Деструктор никогда не вызван

У меня есть класс Class, который создает конструктор Thread в нем. В этом потоке выполняется цикл while(true), который считывает некритические данные из NetStream. Нить будет прервана деструктором:

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

Когда программа хочет закончить использование экземпляра Class - ClassInstance, он вызывает:

ClassInstance = null;
GC.Collect;

Я думал, что это означает, что ~Class() автоматически будет вызывающим в этой точке, но это не так.

Этот поток продолжает работать даже после Application.Exit() и возвращается из Main().

4b9b3361

Ответ 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() будет вызываться, даже если возникает исключение.