[eaf] RE: [eaf] Re: Referencia számlálás

  • From: "Podoski Peter" <pody@xxxxxxxxxxx>
  • To: <eaf@xxxxxxxxxxxxx>
  • Date: Mon, 12 May 2008 04:12:26 +0200

Sziasztok!

Tehát ha jól értem, akkor a probléma röviden annyi, hogy a saját User
Controlok közül jónéhány bent marad a memóriában, vagyis nem hívódik meg a
Dispose()-uk (feltehetően programozói figyelmetlenségből). Nyilván egymásba
ágyazott Control-ok esetén nehéz felderíteni, hogy pontosan hol felejtődött
el a Dispose hívása, vagy egy override esetén a base.Dispose() is könnyedén
kimaradhat.

Küldök két fájlt - ugyan buhera, de nálam az előző levelemben írt
memóriaszemetelő alkalmazásomra simán működött a következő: minden saját
User Controlt a UserControl osztály helyett a RegisterableUserControl
osztályból származtassunk (kecses kis search&replace), aki a konstruktorában
feliratkozik egy központi nyilvántartásba (ControlRegistry).
Diszpozáláskor/finalize-kor pedig leiratkozik onnan, illedelmesen.

Ha megfelelően meghízott az alkalmazásod memóriahasználata, akkor csattanj
bele a debugger-be, és QuickWatch-csal tekintsd meg a ControlRegistry
statikus osztály ControlLocator tulajdonságát, ami egy olyan listából áll,
amiben csak az olyan vezérlők szerepelnek, akiket a GC nem tudott
eltakarítani, mivel még nem diszpozálódtak. Egy listaelem pedig a vezérlő
típusából, szülővezérlőinek listájából és a létrehozásának CallStack-jéből
áll (vagyis a pontos hívási láncból, aminek végén a RegisterableControl
szólt a ControlRegistrynek). A Controlokat hashcode alapján tároltuk, ezért
nincs újabb referencia tárolva, nem hozzuk zavarba a GC-t.

Nekem így sikerült lokalizálnom ezt a mű-szivárgást, úgyhogy szerintem egy
próbát megér.

(Ez a módszer nagyon közel van ahhoz, amit egyébként Ákos körvanalazott)

üdv,
pody


-----Original Message-----
From: eaf-bounce@xxxxxxxxxxxxx [mailto:eaf-bounce@xxxxxxxxxxxxx] On Behalf
Of Zimler Attila Tamas
Sent: Sunday, May 11, 2008 10:38 PM
To: eaf@xxxxxxxxxxxxx
Subject: [eaf] Re: Referencia számlálás

Schmuck Ákos wrote:
>
> Sziasztok!
>
> Ha kicsit késve is, de bekapcsolódnék a beszélgetésbe...
>
> Szóval tesztelgettem a problémát, meg közben Google-özgettem egy
> kicsit, és úgy a második-harmadik kulccsszó kombinációra (c# dispose
> winforms) első helyen kiadott egy lapot amivel a gond megoldható
> (javítható), bár nem autómatikusan, de azért talán kezelhető mértékben.
> A link:
>
http://www.reflectionit.nl/Blog/PermaLink7a2db2fc-eff3-4ad0-9e8c-2a8392a60c2
9.aspx
>
> A lényeg: Modálisan megjelenített ablakoknak a Dispose fv-e nem
> hívódik meg autómatikusan!
>
> Csináltam egy kis példaprogit (előre is bocs, egy-két elég csúnya
> dolgot csinálok benne :) ami szépen demonstrálja a dolgot. Mivel ha
> jól rémlik TabControl / TabPage-ekről volt szó, én is egy TabControl-t
> tettem fel, egy gombbal lehet bele pakolgatni TabPage-eket, minden
> TabPage-en egyetlen származtatott label van, ami nem csinál mást, mint
> konstruktorra kiírja, hogy ő létrejött, Dispose-ra meg kiírja hogy
> halálán van :)
>
> Alul van három gomb, szépen meg lehet figyelni, hogy a sima Show, ill.
> a "módosított" ShowModal szépen műxik, de a sima ShowModal-ra egy
> Dispose sem hívódik meg (direkt kiraktam egy gombra a GC.Collect()
> -et, de persze ez nem a gond forrása, nem is segít)
>
> A "módosítás" egyébként csak annyi, hogy egy 'using' blokkba raktam a
> form megjelenítését. Tehát ha minden helyen, ahol modálisan
> jelenítetek meg ablakot, átírjátok using-ra (vagy a cikkben mutatott
> try/finally-s megoldásra) akkor minden olyan leak-et bezártatok, ami a
> control-okhoz kötődik (saját osztályok persze maradnak, de ha azokat
> pl. a formon deklaráltátok, akkor a form Dispose-át lehet
> override-olni, és be lehet oda tenni a saját osztályok Dispose-át)
>
> Nem tudom ez mennyi munka, de talán kevesebb, mint leak-eket keresgélni...
>
Az eredeti problémának semmi összefüggése nincs modális ablakokkal, a
TableLayoutPanel.Controls.Clear() kapcsán került elő a probléma.

--
Az eletben vannak szabalyok
Nehanyat meg lehet kerulni, a tobbit pedig meg lehet szegni
(The Matrix)

-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: OpenKeyServer v1.2
Comment: Extracted from belgium.keyserver.net

mQGiBEE/HTYRBACInacMGc8B/lkX3CXh5D9wUDlmza4hoatNZjJmWNnPWC9c0h2V
Pi9zYJ1bVqeUnNDzCRpovPdwrswWvb9WpVOS0A7TafzWxCYud6gN8g5KiC7cSuJV
cxI0uv3jWSjUkLnECqTpm9piM4WptXrdFLxUyrKPu+Nl82QSjfdbB+8xXwCg+rWO
KeOZZGGGIYWu+rZ0M/MqyCUD/jqQVc7alF8+5y//zNDml3THTc3ljK8CLcHxFv6F
uADmvHSePRTR1ACBV3dTxL8awvanRsXC25LOuYgM1hF9RRwof59xNtXVttvo724b
FDR+oRDVKyoEm6Istfn9xbbNqRqPfk83SHvect/hlHsfgaTBKd+IRAEkbMIdRin/
E45fA/0bzklqLGj4a8qSxMMvZ1Ib+2RmH5I6o9o33FY3Nmc7IEXJizyTyQaASYCx
ldjuRmfWSOP3gQRrH5owuZq5KBCC/PNMNnUsA4noFmvIilA/9UYqMtQ0BNr+miIM
9/LV70ZbBHVpDLjb2baMjbKrmCFf9rjlc17fFsWk5ZJuE3/UxLQrWmltbGVyIEF0
dGlsYSBUYW3DoXMgPGhpamFzenVAaGxmc2xpbnV4Lmh1PohXBBMRAgAXBQJBPx02
BQsHCgMEAxUDAgMWAgECF4AACgkQ+3OFVYni0PSysACffD6VDtoer3aBe8Gpvn8e
gwiNNH8AmwcmBIrZeCOeobIuZZp3CCOgQblIuQENBEE/HTkQBACKNzv7gS0fgeiO
AMoss2bbO7X9GuO3ufc3+zx2yGV7SvYAuyWLOSMhs1ZYBVp6IawDY88zEx81oxRj
Jd4zO8h/3BI9cmjZ4NAmdv7lWjTs0I0ijFnemcFQaffxN8vIB9DqA4oyJ0A/bS7J
0E/i13sr7hRgB6NZbpy4PSSkczggvwADBQP8D3eakqeJHf1yLmw1XmQVDxHLxtrI
VL0TShFEfMGU/MLfKpcmrd43EsSondsNNm8jv4b9Y/Wgc8XPQQrfPodC5mKt8mQC
F/ovWBFs46YvhS5pojQmp6i9octTWw2OqVn3QgrBFqm4QvayLE9wRLm/xgaFApVI
4RiOOEA6SidURGCIRgQYEQIABgUCQT8dOQAKCRD7c4VVieLQ9HoZAJ4xR5Y1qlnC
LTn1GkbEW0Va+72EqwCfQTP37ylZGrvpYnFoewBIMuLFHM8=
=K0/l
-----END PGP PUBLIC KEY BLOCK-----


using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;

namespace MemoryLeakTester
{
    public static class ControlRegistry
    {
        public class ControlLocatorInfo
        {
            public string ControlTypeInfo;
            public string Parents;
            public string CallStack;
            public ControlLocatorInfo(string t, string p, string c)
            {
                ControlTypeInfo = t;
                Parents = p;
                CallStack = c;
            }
        }

        private static Dictionary<long, ControlLocatorInfo> controls = new 
Dictionary<long, ControlLocatorInfo>(1024);

        public static void Register(Control control)
        {
            if (!controls.ContainsKey(control.GetHashCode()))
                controls.Add(control.GetHashCode(), 
                    new ControlLocatorInfo(control.ToString(), "", new 
StackTrace(2).ToString()));
        }

        public static void Unregister(Control control)
        {
            if (controls.ContainsKey(control.GetHashCode()))
                controls.Remove(control.GetHashCode());
        }

        public static void Update(Control control)
        {
            if (controls.ContainsKey(control.GetHashCode()))
            {
                controls[control.GetHashCode()].Parents = 
GetParentsTree(control);
                controls[control.GetHashCode()].ControlTypeInfo = 
control.ToString();
            }
        }

        private static string GetParentsTree(Control ctrl)
        {
            StringBuilder sb = new StringBuilder();
            while (ctrl != null)
            {
                if (ctrl.Parent != null)
                    sb.AppendLine(ctrl.Parent.ToString());

                ctrl = ctrl.Parent;
            }
            return sb.ToString();
        }

        public static List<ControlLocatorInfo> ControlLocator
        {
            get
            {
                // Force finalize
                GC.Collect();
                List<ControlLocatorInfo> list = new List<ControlLocatorInfo>();
                foreach (KeyValuePair<long, ControlLocatorInfo> kv in controls)
                {
                    list.Add(kv.Value);
                }
                return list;
                
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace MemoryLeakTester
{
    public class RegisterableUserControl : UserControl
    {
        public RegisterableUserControl()
        {
            ControlRegistry.Register(this);
        }
        protected override void Dispose(bool disposing)
        {
            ControlRegistry.Unregister(this);
            base.Dispose(disposing);
        }
        protected override void OnParentChanged(EventArgs e)
        {
            base.OnParentChanged(e);
            if (Parent != null)
                ControlRegistry.Update(this);
        }
        ~RegisterableUserControl()
        {
            ControlRegistry.Unregister(this);
        }
    }
}

Other related posts: