rozhrani IDisposable, GC, dispose...   zodpovězená otázka

Offtopic

uvítal bych jakýkoliv článek (nejlépe vb.net) na téma uvolňování zdrojů (managed/unmanaged). Chování GC a nějaké to polopatické vysvětlecí co se s objekty děje, kde nastávají problémy a chyby programátora a jak tomu zabránit - mrtvolky v paměti.

Jak používat rozhrani IDisposable.

Jaký je rozdíl mezi dispose a finalize. Co kdy volat a nevolat.

Sám píšu delší dobu pro Compact framework a tohle téma mě velmi dlouho nezajímalo, aplikace typu hello world moc paměti nezaberou. Teď jsem ve stádiu, kdy netuším co a proč volat, četl jsem toho hodně, ale pořád tomu nerozumím. Já si otestoval, kam co napsat, aby mi aplikace nesežrala všechnu paměť, ale moudrý z toho tedy nejsem.

článků by to mohlo být i několik, z pohledu implementace vlastí disposable metody (co do ní napsat, jak uvolňovat...) a z pohledu používání .dispose . Já praktikuji zavírání do try, catch, finally kde ve finally disposuji použité zdroje a potom všechno nastavuji na nothing. s pamětí je to ok, ale je to strašná slohová práce a taky se mi to nezdá po stránce programátorské. takové strašlivě nesystémové a buhví kolik to sežere výkonu.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

No, článek na toto téma by se sem určitě hodil, nic pořádného o tom nemáme, jestli budu mít čas, tak ho napíšu.

Abyste měl klidnější spaní, zkusím popsat úplné základy v jednom odstavečku už teď. O drtivou většinu věcí se stará Garbage collector, vy nemusíte většinou řešit vůbec nic. Pokud pracujete se soubory (StreamReader, StreamWriter) nebo s databázemi (SqlConnection), používejte blok using. Ten na těchto objektech automaticky zavolá Dispose, a to i v případě, že z using bloku jakkoliv vyskočíte (výjimka chycená mimo blok, goto, return atd.). Finalize je specialita VB.NET, jedná se o metodu, která se automaticky volá při zániku objektu (destruktor). Volat zvnějšku byste ji nikdy neměl, ony se v .NETu destruktory obecně moc nepoužívají, protože se volají bůhví kdy (až když se objekt ruší, což ale dělá GC a nedá se to moc ovlivnit). Místo nich je právě IDisposable, které umožňuje volat metodu Dispose v okamžiku, kdy přestáváte s objektem pracovat.

Co se týče paměti, GC si ji uvolňuje sám, když ji potřebuje, a moc si do toho kecat nenechá (můžete ho o to požádat zavoláním GC.Collect, ale tuhle metodu potřebují lidé pouze ve speciálních případech a nebo v případě, že píší jako prasata). Většinou platí, že se o to nemusíte starat. Jediné, kdy se o paměť starat musíte, jsou případy, kdy používáte COM objekty - ty musíte většinou uvolňovat explicitně.

Většině lidí se ze začátku nelíbí, že do .NETu nevidí - když píšete v C++, tak tam se naalokuje přesně tolik paměti, kolik chcete, a všechno si děláte sám a máte nad tím 100% kontrolu (což je v mnoha případech na škodu, protože to nemusíte vždy uhlídat). .NET si naalokuje daleko víc paměti, nechává si rezervu, a když mu paměť dojde, zavolá GC atd, prostě se o to stará sám. Mě to také ze začátku trochu vadilo (resp. ze začátku ne, ale až ve chvíli, kdy jsem do .NETu trochu víc proniknul, bál jsem se, jak je všechno pomalé). Ale jakmile jsem pořádně zjistil, jak to uvnitř funguje, tak už se bát přestávám.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Souhlasím až na dvě věci: První věc, Finalize, není specialita VB.NET, nýbrž VB.NETí implementace destruktoru podle CLI specifikace. Například C# má rovněž destruktor, mimořádně debilně pojmenovaný znakem ~. Druhá věc je, že metoda Dispose z rozhraní IDisposable nenahrazuje destruktor, nýbrž slouží výhradně k uvolnění Unmanaged zdrojů spravovaných danou instancí objektu. Pokud se implementuje IDisposable, měl by se zároveň implementovat i destruktor Finalize, ve kterém se uvolní Unmanaged zdroje pomocí zavolání Dispose metody. Význam Dispose metody je také v tom, že pokud již nepotřebujeme s objektem pracovat, můžeme Unmanaged zdroje explicitně uvolnit ihned (a tím zbytečně nezabírat systémové prostředky), aniž bychom čekali až se uráčí Garbage Collector...

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

To "mimořádně debilní" ~ v C# pochází z C++, kde se normálně používá, podle mě je to věc zvyku. Tím, že Finalize je specialita VB.NET, jsem myslel, že v C# žádné Finalize není, ale je tam normální destruktor. Asi jsem to pořádně nerozepsal.

Rozhraní IDisposable v .NETu destruktor svým způsobem nahrazuje, protože v destruktoru máte za úkol uvolnit unmanaged paměť a systémové zdroje. To se v .NETu dělá kvůli tomu, kdy se destruktor spouští, právě v Dispose, aby si to člověk mohl zavolat explicitně sám. Je to tedy taková obezlička .NETu (nemíněno vůbec ve zlém), která umožňuje uvolnit zdroje (tedy "jakoby zavolat destruktor", naprostá analogie k volání delete v C++) v okamžiku, kdy by se to normálně dělalo v C++ (tedy když už objekt nepotřebujeme a je potřeba uvolnit všechno, co si nahamtal). Takže dá se to vyložit i jako náhrada destruktoru, protože v .NETu jsou destruktory už ze své podstaty omezeně použitelné, nemáte možnost ovlivnit, kdy se volají.

Jinak samozřejmě máte naprostou pravdu.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

děkuji za odpověd.

byl jsem zvyklý hlídat si paměť a když jsem se dozvěděl, že .Net má "něco" co se o to postará automaticky, hodil jsem piplání s pamětí za hlavu a užíval si programování.

Skončilo to tak, že jsem napsal "obří" aplikaci, která masivně pracuje s grafikou (coredll) a tam jsem narazil. Stačilo v aplikaci hodinku klikat a veškerá dostupná pamět zmizela (cca 30MB) a potom aplikace padla na outofmemory exception. Tak jsem se dostal poprvé trošku pod pokličku .netu a zjistil jsem, že obsahuje plno zajímavostí.

pokusem jsem dospěl ke konstrukci, která chová korektně v paměti:

Dim gr As Graphics = Nothing
Try
 gr = Graphics.FromImage("aa.bmp")
Catch ex As Exception
'todo
Finally
If Not gr Is Nothing Then gr.Dispose()
gr = Nothing
End Try

Ještě mě napadá obyčejný datový typ string. Četl jsem, že při každé modifikaci stringu, vznikne kopie v paměti a původní se nastaví ke smazání GC. GC se pouští občas, takže by se mohlo stát, že i tady bude problém s pamětí. Když nechci použít stringbuilder, jak se takového chování vyvarovat? Po použití nastavuji string proměnou na nothing (nebo bych měl na string.empty?), ale netuším jestli to je nějak prospěšné.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

To zahlcení paměti jste si způsobil zřejmě sám nevědomě. Garbage Collector odstraní nepotřebnou instanci objektu z paměti pouze v případě, že na tuto instanci neexistují už žádné další reference (pomocí mechanismu zvaného Reference Counting).

Příklad neuvolnění instance objektu:

Dim obj As New Object
Dim objRef As Object = obj

obj = Nothing

Vytvoří se nová instance objektu a do obj se uloží reference na ni. Do objRef se uloží rovněž reference na novou instanci objektu (objRef se odkazuje na stejné místo jako obj). obj se nastaví na Nothing, tudíž neukazuje nikam a nemá žádnou hodnotu. Garbage Collector nyní nemůže z paměti uvolnit původní instanci objektu, protože ji stále ještě referencuje proměnná objRef.

Co se týče uvolňování Managed prostředků, nemusíte se o uvolňování vůbec starat (pokud ovšem nereferencujete instance ze stopadesáti různých míst, na které zapomenete - pak vzniká situace popsaná výše).

Co se týče uvolňování Unmanaged prostředků (Managed třídy využívající Unmanaged zdroje, většinou věci ze System.Drawing.*, System.IO.* apod.), je nejlepší řešení používat konstrukci Using...End Using (funguje pouze u tříd implementujících rozhraní IDisposable), kde je zaručeno automatické uvolnění Unmanaged zdrojů - nemusíte tedy volat Dispose ani proměnnou nastavovat na Nothing.

Co se týče Stringů, je při každé změně (nebo vytvoření) proměnné typu String vytvořená nová instance (pokud ještě neexistuje) a ta uložena do interní hašovací tabulky, ze které je při opětovném použití rovnou vyzvednuta (je to kvůli zvýšení výkonu). Toto chování nelze nijak změnit nebo ovlivnit, proto je třeba s velkým množstvím textových řetězců pracovat s rozmyslem a snažit se maximálně využívat StringBuilder. Po použití proměnné typu String s ní nemusíte tedy dělat vůbec nic. Ještě bych poznamenal, že je velký rozdíl mezi hodnotami "" a String.Empty: "" vytváří vždy novou instanci (což je samozřejmě náročnější), kdežto String.Empty je konstanta natvrdo vložená do kódu.

Obecně má .NET Framework velmi dobrou správu paměti a pokud používáte Using...End Using všude kde to de a v ostatním kódu vyloženě neprasíte, není třeba se o uvolňování paměti vůbec starat...

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.
  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback