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

  • From: "Schmuck Ákos" <kossch@xxxxxxxxx>
  • To: eaf@xxxxxxxxxxxxx
  • Date: Sun, 11 May 2008 14:00:52 +0200

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-2a8392a60c29.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...

Remélem segített ;)

Ákos
PS.: Ha mindenképpen autómatizálni kéne, akkor vmi olyasmit csinálnék, hogy
lenne egy singleton, és egy származtatott form. A form létrehozásakor
felregisztrálja magát (akár call-stack-el együtt) a singleton-nál,
bezáráskor leregisztrálja magát. A Singleton a program végén minden le-nem
regisztrált formot jelentene. Ez után már csak a kódban előforduló 'Form'
típusokat kéne lecseréni az új típusra. Persze itt a dolog neheze, de hát
egy felügyelt Replace csak nem tart olyan sokáig (vagy legalábbis rövidebb
ideig mint tovább bogarászni a kódot, hogy hol van a baj...?! :) Minden más
autómatizálással kapcsolatos ötletem ugyanez, csak futásidőben Reflection +
kódgenerálással, rövid távon ennél azért egy sima Replace egyszerűbb lesz :D

PS2.: a progi VS2008-as, de .NET 2.0 WinForms-os projekt. Link:
http://people.inf.elte.hu/kossch/MemTest.zip

PS3.: .NET-es leak-keresgéléshez két cikk (ha mégis kéne :)   :
http://www.codeproject.com/KB/dotnet/Memory_Leak_Detection.aspx
http://www.codeproject.com/KB/debug/WinDBGAndSOS.aspx

2008/5/8 Zimler Attila Tamas <hijaszu@xxxxxxxxxxxx>:

> Podoski Peter wrote:
>
> > Helló!
> >
> > Köszi a részletes leírást, így már világosabb a helyzet. Továbbra is
> > mindenkit óva intenék ilyen jellegű problémára .NET-es referencia számláló
> > implementációtól. A Control-okra bizony hívogatni kell a Dispose()-t,
> > amennyiben jelentős mennyiségű erőforrást foglaltál le bennük. (Egyáltalán,
> > minden IDisposable-re illik Dispose()-t mondani, amikor már nem használod,
> > lásd "using" blokkok, mint burkolt try-catch-finally automatikus diszpozáló
> > nyelvi elem).
> >
> >
> >
> Nyilván, de addig is meg kellene találnom, hogy hol nincs meghívva. Nem
> azt mondom, hogy a referencia számlálás azt oldaná meg, hogy ne kelljen
> meghívni. A referencia számlálás azt oldaná meg, hogy megtaláljam, mire
> nincs meghívva.
>
> > A Controls.Clear() nagyjából ugyanazt csinálja mint egy
> > ArrayList.Clear() - mivel ő egy ArrayList-et használ belül (amellett, hogy
> > az IList-et implementálja), gyakorlatilag tényleg csak egy listából tűnik el
> > a referenciád. Ha csak ennyit "takarítasz", akkor ugyan már nincs
> > referenciád a programodon belül a Control-ra, viszont egy Win32 Handle rajta
> > van a Controlon (ami a form Handle-je is). Így a TableLayout-ba pakolt
> > Control-ok Dispose() hívás nélkül a TableLayout szülő formjának megszűnéséig
> > élni fognak, a GC nem fogja őket halottnak nyilvánítani. Ha Dispose()-t
> > hívsz rájuk, akkor szemétté alakulnak (valószínűleg nem szabadulnak fel).
> >
> > Az egy teljesen más kérdés, hogy miért nem csökken a program
> >  feladatkezelőben is látható memóriahasználata. Amikor a GC takarít, akkor
> > igazából csak összegyűjti a szemetet (megjelöli őket), igazi kidobás csak
> > memóriafoglaláskor történik - by design. Készítettem egy példaprogramot,
> > amiben egy olyan UserControl volt, aki 50 megás tömböt foglalt le születése
> > után (és használta is). Beraktam 20 darabot a TableLayoutPanel-be, majd
> > Dispose() hívás után kivettem őket, és ezt megismételtem jó sokszor, közben
> > nézve és kiíratva a formra a memóriahasználatot. A helyzet az, hogy a GC
> > elég okos ahhoz, hogy eldöntse, hogy mit műveljen a memóriával, nagyon nem
> > kell aggódni miatta (a programozó az értékes idejét inkább az üzleti logika
> > implementálása fordíthatja - ez az elsődleges "aspektus").
> >
> >
> Nem triviális controllunkban kellően sok hiba van ahhoz, hogy ez ne
> történjen meg (mármint, hogy a GC ne tudja automatikusan felszabadítani a
> hibásat).
>
> > Nem ismeretlen számomra a C++-os memóriakezelés szabadsága, de itt
> > egyszerűen nem kell megijedni, hogyha a 200 megányi tényleges
> > memóriahasználat a feladatkezelőben 800 megának látszik, majd újabb 50
> > foglalásakor 500 lesz, és újabb 50 mega foglalásakor 300, és a Dispose()
> > után is marad 300.
> >
> >
> Nagyszerű, mi 1,5G-nál járunk, de ez engem nem érdekelne annyira (később
> ki kellene amúgy is optimalizálni a kódot és folyamatban is van az
> újraírása). Ami zavar, hogy elfogy a window handle.
>
> > Még egy dologra regálnék:
> > "(Ennyit arról, hogy majd a nyelv valamit megcsinál a programozó
> > helyett.)"
> >
> >
> Ez nem a csak a keretrendszerre utalt, hanem arra,  hogy a keretrendszer
> milyen szemléletet ültet a programozókba. Mivel a legtöbb helyen nem kell a
> memória felszabadítással foglalkozni, programozóink ott se gondoltak arra,
> hogy kellhet, ahol kivételesen kellett volna (és ezért  nem is teljesen
> hibáztathatók). Ha már azt mondta a rendszer, hogy nem kell felszabadítani,
> ebben az esetben is megcsinálhatta volna eszerint a filozófia szerint.
>
> > Dispose()-t neked kell hívni a Control-ra, minden mást megold a
> > keretrendszer. Ha mégis memory leaked van, akkor bizonyára a kódban van a
> > hiba, és ezt addig fent is tartom, ameddig nem tudsz mutatni egy olyan
> > mintaprogramot, ami ténylegesen leakel, pedig rendeltetésszerűen van
> > diszpozálva benne mindenki. (nem kell éles projektből részlet, ki lehet
> > emelni belőle a lényeget)
> >
> >
> Nem fogok példaprogramot adni, mert ha redukálom már nem fog szivárogni,
> az éles kódot pedig nyilván nem kaphatja meg senki. Bár lehet, hogy majd
> megpróbálok demonstrációs kódot írni.
>
> > (Az aktuális memóriahasználtot a GC.GetTotalMemory(false) hívással tudod
> > kideríteni - de ezt biztos ismered.)
> >
> >
> Ezt mint mondtam még profiler eszközzel is ki tudom deríteni (és nem
> szivárog - a szó klasszikus értelmében -, mert pont az a baj, hogy a
> szemétgyűjtő nem hajlandó valamiért összeszedni), épp csak nem tudom
> meghatározni, mely objektumok vannak még legálisan a memóriában és melyekre
> nincs már referenciánk és mégis ott vannak (tehát disposolnunk kellett
> volna). És hogy ne legyen triviális egy naptár modulról van szó, amely
> többszáz napot és a megfelelő adatait tartja a memóriában (úgy kb 300 ezer
> objektum péládonyonként).  Azt hiszem ilyen nagyságrendben azért nem működik
> az egyesével megvizsgálom, "te élsz még?" megközelítés.
>
> H.
>
> --
> 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-----
>
>
>

Other related posts: