2015-08-19 18 views
6

Mam obiekt zawierający wątek roboczy. Chciałbym zabić wątek, gdy obiekt jest poza zakresem.Jak niejawnie zatrzymać wątek w obiekcie w C#

using System.IO; 
using System; 
using System.Threading; 

namespace tt { 
    class Program 
    { 
     static void Main() 
     { 
      AnotherClass a = new AnotherClass(); 
      a.Say(); 
     } 

    } 

    class AnotherClass:IDisposable { 
     private bool m_Disposed; 
     private readonly AutoResetEvent m_ResetEvent = new AutoResetEvent(false); 

     public AnotherClass() { 
      Thread t = new Thread(wait); 
      t.Start(); 
     } 

     public void Dispose() { 
      Dispose(true); 
     } 
     private void Dispose(bool disposing) { 
      if (m_Disposed) { 
       return; 
      } 

      if (disposing) { 
       Console.WriteLine("inner disposing"); 
      } 
      m_ResetEvent.Set(); 
      Console.WriteLine("Outer disposing"); 
      m_Disposed = true; 
     } 

     private void wait() { 
      m_ResetEvent.WaitOne(); 
     } 

     ~AnotherClass() { 
      Dispose(false); 
     } 

     public void Say() { 
      Console.WriteLine("HellO"); 
     } 
    } 
} 

Program będzie tylko powiesić tam, ponieważ destruktor z AnotherClass nie nazywa. Wiem, że mogę wywołać a.Dispose(), aby zabić wątek. Ale czy istnieje sposób na nieumyślne zabicie wątku, gdy przedmiot wykracza poza zakres?

+0

Myślę, że jedynym sposobem na niejawne zatrzymanie wątku jest odłączenie zasilania. –

Odpowiedz

6

Nie, to nie jest możliwe. Nie ma liczenia odwołań, które mogą zauważyć, że obiekt nie ma więcej odniesień, więc nie ma sposobu, aby zrobić cokolwiek, gdy tak się stanie.

Interfejs IDisposable jest używany dla klas, które muszą coś zrobić, gdy instancja nie będzie już używana, a wywołanie metody Dispose jest sposobem sygnalizowania wykonania instancji.

Zwykłym sposobem, aby upewnić się, że obiekt jest umieszczony po opuszczeniu Zakres to owinąć kod w using bloku:

static void Main() 
{ 
    using (AnotherClass a = new AnotherClass()) 
    { 
    a.Say(); 
    } 
} 

Blok using jest cukier syntaktyczny dla try...finally bloku:

static void Main() 
{ 
    AnotherClass a = new AnotherClass(); 
    try 
    { 
    a.Say(); 
    } 
    finally 
    { 
    if (a != null) 
    { 
     ((Idisposable)a).Dispose(); 
    } 
    } 
} 
+0

Ye bogowie i małe ryby, uwielbiam "używać". Zapamiętywanie nazwy "Dispose" nie stanowi problemu, ale "używanie" wygląda tak cholernie elegancko w kodzie. –

+0

, więc jeśli usługa AnotherClass jest biblioteką, nie ma możliwości upewnienia się, że użytkownik prawidłowo zamyka/zadaje program AnotherClass? –

+0

@MonsterHunter Jeśli implementuje IDisposable, to istnieje. Jeśli nie masz dostępu do logiki w drugiej klasie, a dział IT nie będzie się prawidłowo utylizować, możesz przejrzeć inną bibliotekę. – Cory

1

Można użyć obsługi wyjątków i używać błąd wyświetlany jako wyjątku w bloku catch

2

Dodawanie do odpowiedzi Guffa jest, myślę, że ważne jest również, aby pamiętać, że nawet jeśli miałeś sposób wykryć Dokładny moment, w którym instancja AnotherClass wyszła poza zasięg, w tym przypadku nigdy nie będzie. I dlatego zauważyłeś, że twój destruktor nigdy nie został wywołany.

Powodem tego jest to, że podczas tworzenia i rozpocząć wątek roboczy, nitka przechodzi odniesienie delegata wskazującego na metody instancji wait() która pochodzi z niejawnego odniesieniu do this (The instancji AnotherClass). Dopóki twój wątek nie wykracza poza zakres, będzie zawierał silne odniesienie do instancji AnotherClass i zapobiegnie gromadzeniu się śmieci.

+0

Jak mogę się upewnić, że AntherClass nie spowoduje zawieszenia się programu bez dotykania kodu w Klasie Programu? –

+1

Na podstawie dostępnych informacji najlepszym sposobem jest użycie wzoru "IDisposable" zgodnie z sugestią Guffa. Ale masz również rację pod tym względem, że jeśli wywołujący nie wywoła "Dispose()", to nie zadziała. Istnieje kilka obejść, które wymagają użycia finalizatora, ale te opcje rzadko są dobrym pomysłem i mogą prowadzić do innych poważniejszych problemów. Gdybym był w twoich butach, rozważałbym zmianę mojego projektu klasowego. Ale nie wiedząc, jakie są wymagania biznesowe, trudno zaproponować lepszą alternatywę. – sstan

1

Jeśli twój wątek istnieje w celu obsługi obiektu na rzecz innych wątków, powinieneś mieć publiczny obiekt opakowania, do którego wszystkie wątki, które są "zainteresowane" usługą, będą zawierały silne referencje, a sama usługa będzie posiadać długie słabe odniesienie. Ten obiekt opakowania nie powinien zawierać żadnych informacji, które usługa potrzebuje do odczytu lub manipulowania, ale zamiast tego powinien przekazywać wszystkie żądania informacji do obiektu, który zarówno opakowanie, jak i usługa posiadają silne referencje.

Gdy wątek serwera jest przebudzony, powinien okresowo sprawdzać, czy słabe odniesienie do obiektu opakowania jest nadal aktywne. Jeśli tak nie jest, wątek serwera powinien zostać zamknięty w uporządkowany sposób. Zauważ, że serwer nigdy nie używa silnego odwołania do obiektu opakowania - nawet tymczasowo - więc obiekt opakowania powinien zostać odparowany, jak tylko nikt nie jest nim zainteresowany.

Jeśli wątek serwera może być zablokowany w nieskończoność, czekając, aż różne rzeczy się wydarzą, opakowanie powinno zawierać jedyne odniesienie w dowolnym miejscu we wszechświecie do obiektu, który jest finalizowalny i ma silne odniesienie do obiektu serwera; kiedy uruchamia się finalizator, powinien sprawdzić, czy długi słaby referencja przechowywana przez obiekt serwera jest nadal ważny. Jeśli tak nie jest, powinien spróbować przesunąć wątek serwera (np.przy użyciu Monitor.TryEnter/Monitor.Pulse. Jeśli obiekt opakowania jest nadal aktywny (finalizatory mogą czasami powodować fałszywe wyzwalanie w niektórych scenariuszach wskrzeszenia) lub finalizator nie może przesuwać wątku serwera, obiekt finalizowalny powinien ponownie zarejestrować się w celu sfinalizowania.

Użycie obiektu finalizatora spowoduje komplikację; jeśli wątek serwera może się budzić okresowo, bez potrzeby kłucia, to uprości to projekt. Jednakże, jak opisano, finalizator powinien być względnie odporny nawet w scenariuszach obejmujących mimowolne zmartwychwstanie.