2013-06-26 23 views
6

Mam aplikację CF, która z czasem wycieka UserControls. Zajęło to trochę czasu, ale zawęziłem go, a nawet replikowałem zachowanie w pełnym kontekście (3.5). Ponieważ zachowanie istnieje w obu, nie chcę tego nazwać błędem, ale na pewno nie rozumiem, dlaczego tak się dzieje i mam nadzieję, że ktoś może rzucić trochę światła na to.GC nie finalizuje UserControl?

Tworzę więc prostą aplikację WinForms z formularzem i przyciskiem. Kliknięcie przycisku przełącza się między utworzeniem nowego kontrolera UserControl a usunięciem tego elementu sterującego. Bardzo prosta.

public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 
    } 

    UserControl1 m_ctl; 

    private void button1_Click(object sender, EventArgs e) 
    { 
     if (m_ctl == null) 
     { 
      m_ctl = new UserControl1(); 
      m_ctl.Visible = true; 
      this.Controls.Add(m_ctl); 
     } 
     else 
     { 
      this.Controls.Remove(m_ctl); 
      m_ctl.Dispose(); 
      m_ctl = null; 
      GC.Collect(); 
     } 
    } 
} 

I tutaj jest UserControl. Po prostu śledzi liczbę aktywnych (tzn. Nie sfinalizowanych) wystąpień. Nie ma w nim nic poza pojedynczą etykietą, więc mogę wizualnie potwierdzić, że jest w Formie.

public partial class UserControl1 : UserControl 
{ 
    private static int m_instanceCount = 0; 

    public UserControl1() 
    { 
     var c = Interlocked.Increment(ref m_instanceCount); 
     Debug.WriteLine("Instances: " + c.ToString()); 

     InitializeComponent(); 
    } 

    ~UserControl1() 
    { 
     var c = Interlocked.Decrement(ref m_instanceCount); 
     Debug.WriteLine("Instances: " + c.ToString()); 
    } 
} 

Dziwne jest to, że liczba instancji rośnie w nieskończoność. W końcu na urządzeniu zabrakło mi pamięci. Podejrzewam, że również na PC, nie jestem skłonny do klikania przycisku na następny rok.

Teraz gdybym zmienić domyślną, projektant generowane Dispose metody usercontrol w ten sposób, po prostu dodając wywołanie ReRegisterForFinalize:

protected override void Dispose(bool disposing) 
{ 
    if (disposing && (components != null)) 
    { 
     components.Dispose(); 
    } 

    base.Dispose(disposing); 

    if (disposing) 
    { 
     GC.ReRegisterForFinalize(this); 
    } 
} 

Wtedy zachowuje się dokładnie tak, jak oczekiwano, Finalizacja wystąpień podczas odbioru (gdy ręczne lub automatyczne) .

Dlaczego tak się dzieje? Najwyraźniej baza nazywa SuppressFinalize, ale dlaczego tak się dzieje i dlaczego w imieniu Odina jest to domyślne zachowanie?

+0

+1 do rzeczywiście ma prawdziwy powód do rozważenia przy użyciu GC – Sayse

+0

Zważywszy Reeda (przepraszam nie może być pomocne na rzeczywiste rozwiązanie!) odpowiedź, co robi finalizator w twoim prawdziwym kodzie? Czy wywołanie finalizatora nie mogło spowodować wycieku? – svick

+0

Rzeczywisty kod nie ma żadnych finalizatorów. Posiadają Dispose wdrożone, a bitmapy i podobne są usuwane. – ctacke

Odpowiedz

4

Dlaczego tak się dzieje? Najwyraźniej baza nazywa SuppressFinalize, ale dlaczego tak się dzieje i dlaczego w imieniu Odina jest to domyślne zachowanie?

Jest to domyślne zachowanie dla klas (prawidłowo) implementujące IDisposable. Podczas wywoływania funkcji IDisposable.Dispose domyślnym sugerowanym zachowaniem jest blokowanie finalizacji, ponieważ głównym powodem zakończenia jest wyczyszczenie zasobów , które nigdy nie były usuwane:. Dzieje się tak, ponieważ finalizacja jest kosztowną operacją - nie chcesz niepotrzebnie finalizować obiektów, a jeśli został wywołany Dispose, myślę, że już wyczyściłeś swoje niezarządzane zasoby. Każda zarządzana pamięć zostanie obsłużona niezależnie.

Powinieneś zastąpić Dispose, i zrób swój ubytek w ramach nadpisania Dispose. To jest wyjaśnione w documentation for IDisposable. Próbka Dispose metoda jest realizacja połączeń (z odnośną dokumentacją):

public void Dispose() 
{ 
    Dispose(true); 
    // This object will be cleaned up by the Dispose method. 
    // Therefore, you should call GC.SupressFinalize to 
    // take this object off the finalization queue 
    // and prevent finalization code for this object 
    // from executing a second time 
    GC.SuppressFinalize(this); 
} 
+0

Jedynym problemem jest fakt, że w rzeczywistym środowisku aplikacji pamięć jest indeksowana podczas nawigacji po aplikacji, a GC nigdy nie odzyskuje tej pamięci. Na szczęście wszystkie moje widoki pochodzą z pojedynczej bazy UserControl. Kiedy ReRegisterForFinalize w tej bazie, wyciek znika. Żadne z moich wyświetleń nie zaimplementowało finalizatorów. Przez skojarzenie wydaje się, że jest to przyczyną kłopotów. – ctacke

+0

To, co mnie odrzuciło na tej ścieżce, to używanie programu .NET Memory Profiler, który powiedział, że istnieją widoki, które zostały "usunięte, ale nie sfinalizowane". – ctacke