2013-08-28 30 views
13

Mam klasę Class, która tworzy Thread w swoim konstruktorze. Wątek ten uruchamia pętlę while(true), która odczytuje niekrytyczne dane z NetStream. Nić zostanie przerwana przez destructor:Destruktor nigdy nie zostanie wywołany

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

Gdy program chce zakończyć stosowanie przykład Class „s - ClassInstance, wywołuje:

ClassInstance = null; 
GC.Collect; 

myślałem, co oznacza, że ​​~Class() będzie dzwoniący automatycznie w tym momencie - ale tak nie jest.

Ten wątek działa nawet po Application.Exit() i powraca z Main().

+16

Destruktor klasy C# [nie ma gwarancji, że zostanie wywołany] (http://blogs.msdn.com/b/ericlippert/archive/2010/01/21/what-s-the-difference-betweena-a- destructor-and-a-finalizer.aspx). Użyj opcji [Dispose pattern] (http://msdn.microsoft.com/en-us/library/fs2xkftw.aspx) do deterministycznego czyszczenia zasobów. – Romoku

+2

Zamiast wywoływania GC.Collect użyj instrukcji using() http://msdn.microsoft.com/en-us/library/yh598w02.aspx. Generalnie zaleca się, aby nie dzwonić do GC.Collect -> http: // stackoverflow.com/questions/478167/when-is-it-acceptable-to-call-gc-collect – Oliver

+0

Ustawienie zmiennej instancji na 'null' to * nie to samo * co wywołanie destruktora! Właściwie nie ma praktycznie żadnego wpływu na instancję obiektu, tylko jedno mniej odniesienia. –

Odpowiedz

7

Decydującym kawałek kodu nie jest wliczony w cenę; w jaki sposób wątek jest uruchamiany i jaka metoda jest uruchomiona. Gdybym miał zgadywać, powiedziałbym, że prawdopodobnie rozpocząłeś wątek, przekazując metodę instancji z Class. Więc zasadniczo twoja instancja klasy jest nadal zakorzeniona przez działanie wątku. Próbujesz zatrzymać wątek w finalizatorze, ale finalizator nigdy się nie uruchomi, ponieważ instancja nadal jest zrootowana, co prowadzi do sytuacji catch-22.

Wspomniał Pan również, że wątek zawiera niekrytyczny kod, co było uzasadnieniem dla używania Thread.Abort. To naprawdę nie jest wystarczający powód. Bardzo trudno jest kontrolować, gdzie ThreadAbortException zostanie wprowadzony do wątku i w rezultacie może spowodować uszkodzenie struktur danych programu, których się nie spodziewałeś.

Użyj nowych mechanizmów cooperative cancellation dołączonych do licencji TPL. Zmień pętlę while (true), aby zamiast tego odpytać CancellationToken. Zatwierdź anulowanie w metodzie Dispose po wdrożeniu IDisposable. Nie dołączaj finalizatora (destruktora w języku C#). Finalizatory są przeznaczone do czyszczenia niezarządzanych zasobów. Ponieważ nie wskazałeś, że zasoby niezarządzane są w grze, nie ma sensu mieć finalizatora. Nie musisz dołączać finalizatora podczas implementowania IDisposable. W rzeczywistości uważa się za niewłaściwą praktykę taką, gdy nie jest ona naprawdę potrzebna.

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. 
    } 
    } 
} 
+0

Niedawno zmieniłem kod w GUI, który używał 'BackgroundWorker' do używania' TPL' i był pod dużym wrażeniem. – bhs

5

Jeśli zaimplementujesz IDisposable i usuniesz obiekt, kod w Dispose uruchomi się, ale nie ma gwarancji, że zostanie również wywołany Destruktor.

Garbage Collector tworzy opinię, że to strata czasu. Więc jeśli chcesz mieć przewidywalną utylizację, możesz użyć IDisposable.

Sprawdź to Thread

2

CLR utrzymuje wszystkie działające wątki. Przekażecie InstanceMethod swojej klasy konstruktorowi wątku jako delegatowi ThreadStart lub ParameterizedThreadStart. Delegate będzie zawierał MethodInfo metody, którą zdałeś i Instance swojej klasy w usłudze Target.

Garbage collector zbiera i obiekt, który nie powinien mieć żadnego Strong References, ale twoje wystąpienie wciąż żyje wewnątrz Delegate z Thread. Twoja klasa nadal ma numer Strong Reference, dlatego nie można go używać do zbierania śmieci.

Aby udowodnić to, co podano powyżej

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); 
     } 
    } 
} 

}

Spróbuj powyższy kod. Destructor Nie zostanie wywołana. Aby to działało oznaczyć metodę ThreadProc jako static i uruchom ponownie Destructorzostanie wywołana

2

Nieco off-topic: Można użyć Tasks zamiast nagich wątków, aby uruchomić funkcje bez martwienia się o dyspozycji.

Istnieje wiele problemów tutaj:

  • ustawienie zmiennej null niczego nie usuwać, po prostu usuwa odwołanie do instancji.
  • Destruktor zostanie wywołany tylko wtedy, gdy śmieciarz zdecyduje się odebrać instancję. Śmieciarz działa nieregularnie, zwykle tylko wtedy, gdy wykryje, że istnieje presja pamięci.
  • Śmieciarka zbiera TYLKO osierocone kolekcje. Osierocone oznacza, że ​​wszelkie odniesienia wskazane przez twój obiekt są nieprawidłowe.

Powinieneś zaimplementować interfejs IDisposable i wywołać dowolny kod oczyszczania w metodzie Dispose. C# i VB oferują słowo kluczowe using, aby utylizacja była łatwiejsza nawet w obliczu wyjątków.

Typowym IDisposable realizacji jest podobny do następującego:

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 
    } 
} 

Następnie można użyć klasy tak:

using(var myClass=new MyClass()) 
{ 
    ... 
} 

Gdy using wygaśnięciem blokowych, Dispose() zostanie wywołana nawet jeśli wystąpi wyjątek.

Powiązane problemy