WPF memory leak   otázka

WPF

Vážení kolegové,

již několikátý den se pídím po informacích ohledně WPF a jeho spotřebě paměti. Loni sem zakoupil komponenty pro WPF od Devexpress a celé své gui aplikace pro sklady přepsal do WPF. Práce parádní, šlo to dobře od ruky. Po nasazení se ale začaly dít věci - zjistil sem, že WPF po sobě nemaže objekty z paměti.

Veškeré vlastnosti DependencyProperty se ukládají do statického pole EffectiveValues kde se vesele hromadí po celou dobu běhu programu. Nikde na netu sem nepřišel jak toto obejít.

Krásný příklad je velmi jednoduchý:

Vytvořím ve WPF okno a dám na něj např. tlačítko a controlu s kalendářem.

poté okno několikrát otevřu a zavřu. Po každém otevření okna se navýší využití paměti které ovšem WPf již nevrátí zpět systému a data zůstávají v kolekci EffectiveValues.

For i=0 To 100

dim w as new Window2

w.Show()

System.Threading.Thread.Sleep(500)

w.Close()

Next

Nevíte někdo jak toto řešit? Na diskuzních fórech sem nic nenašel a pro ekonomickou aplikaci je pak tedy WPF naprosto nepoužitelný.

Děkuji a přeji pěkný den

Jelínek Petr

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Nejedná se o problém samotného WPF,ale spíše návrhu aplikace. Na netu je o tom pár relativně názorných článků.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

No právě nějaký článek na toto téma hledám a řešení stále nenacházím. Ovšem nevím, co je to za špatný návrh aplikace otevírat a zavírat okna. Jediné řešení jaké jsem našel je držet ovládací prvky v paměti a recyklovat je, což je v tak rozsáhlé aplikaci naprostý nesmysl.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Dobrý den,

v .Net aplikacích nemusí dojít (a většinou ani nedochází) k uvolnění paměti ihned po zániku objektu.

O čištění paměti se stará Garbage Collector, který se spouští jednou za čas (většinou když dostane od systému dotaz, zda by nebyl tak laskav a neuvolnil kousek paměti). Do té doby zůstávají objekty alokované v paměti (dokud je paměti dost, tak se nebude namáhat s úklidem).

Můžete zkusit zavolat Garbage Collector manuálně a podívat se, zda se něco uvolnilo

   System.GC.Collect()

Má to však tři úskalí:

Za prvé čištění paměti má nemalou režii a pokud by jste GC spouštěl často, bude to mít nepříznivé důsledky na výkon aplikace.

Za druhé, to že zavoláte tuto metodu ještě neznamená, že se ihned provede (vy GC jen požádáte zda by nebyl tak hodný aby až se mu bude chtít, provedl úklid)

A konečně za třetí za svůj život se objekt může dostat do několika režimů. Podle toho v jakém režimu je, tak s ním GC zacházi. Zjednodušeně řečeno pokud je objekt na "nejnižším levelu" GC ho smaže při nejbližší příležitosti. Pokud se objekt dostane do "nejvyššího levelu" GC usoudí že bez něj nejde žít a objekt se uvolní až s ukončením aplikace.

Toto je hodně zjednodušené a omlouvám se za nepřesnou terminologii. Ale funkce GC je poměrně komplikovaná a obecně si .NET framework moc nenechá kecat do toho, jak má hospodařit s pamětí.

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

Dobrý den,

děkuji za reakci, v .NETu programuji již od jeho vzniku a vím jak se GC chová. Jenže GC nemůže ty objekty uvolnit, jelikož sou odkazy na dependecyproperty v kolekci EffectiveValues (windowsbase). To si tam přidává samo WPF při registraci dependencyproperty controlu. Pokud tento control pak někde zobrazíte, wpf přidá do kolekce hodnotu té DP. A pak když okno s kontrolou zavřete, DP stále zůstává v kolekci a GC není schopen toto okno (nebo položkuu v EffectiveValues) smazat.

Mimochodem stejně blbě je chová nová verze visual studia - čím víc otevíráte záložek se zdrojákama, tím víc roste spotřebovaná pamět. Po zavření záložky se ovšem všechna zpět neuvolní.

Řešením je, aby controla (okno) při svém zavírání volala nějakou metodu (destruktor), která by záznamy v EffectiveValues() smazala. Jenže to se ve WPF neděje.

Pokud si otestujete ten jednoduchý příklad co sem dal, zjistíte o tem mluvím. Prostě při každém otevření okna a jeho zavření program sežere pamětˇ. GC nemá na toto chování vliv. A v aplikace mé velikosti, kde uživatel pracuje velmi aktivně a vytváří se několik dokladů je schopný program za 30 min práce naalokovat i 2 GB paměti. MS na tento problém mlží a všechny příspěvky co sem na toto téma našel zahrál do outu s tím že to je feature WPF. Ovšem pro velkou app feature naprosto likvidační.

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

MS na tento problém mlží a všechny příspěvky co sem na toto téma našel zahrál do outu s tím že to je feature WPF.

Klasický přístup Microsoftu k něčemu, co totálně posral. S problémem vám bohužel nemohu pomoct, protože ten paskvil nepoužívám. Spíše by mne velmi zajímalo, co bylo vaším důvodem k přechodu z osvědčeného Windows Forms (obzvlášť v business aplikaci, kde eye candy efekty nehrají žádnou roli).

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Popravdě řečeno sem dlouho rozmýšlel s přechodem. Měl sem napsaný svoje UI komponenty do WinForms, které toho neuměly mnoho ale zato byly mnohonásobně rychlejší. Jenže klient si usmyslel nějaké vychytávky a byl sem před rozhodnutím jaké komponenty koupit. Také sem potřeboval trochu urychlit vývoj.

S WPF sem si již nějakou dobu hrál a musím říct, že plno věcí je tam dobře vychytaných (RoutedEvents, DataBinding) a právě u ekonomických app to parádně fungovalo. Takže padlo rozhodnutí když už se to bude přepisovat tak rovnou do WPF (i MS šel s VisualStudiem do WPF). App sem přepsal, na testování byla dobrá, UI pomalejší na jednojádrových PC ale zase to vyvážil komfort v užívání. A nyní přišlo rozčarování. Nechápu, proč MS neudělá relativně jednoduchou opravu (nějakou metodu která by se i ručně mohla volat a která by ta data z toho pole vymazala). VS se chová stejně blbě - při zavření tabu se zdrojákem neuvolnuje paměť. A MS mlčí. MSDN mi bohužel skončilo a momentálně není důvod, krom tohoto zjištění, si jej kupovat. Žádné novinky nás moc nečekají a většina nových věcí je pro mě k ničemu.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Takže sem asi došel na to, proč se to chová tak, jak se to chová. Chyba není ani tak ve WPF ale v GC. GC totiž odmítá sám samovolně odstraňovat výše zmíněné objekty z kolekce a nechává je tam zabírat místo. Pokud GC zavoláte po zavření okna (ihned po něm), GC stejně toto z paměti neodstraní (ani po hodině IDLE app). Řešením je vytvořit timer, který jednou za čas zavolá GC.Collect(). A pak si GC občas ty objekty smaže. Pokud ho nezavolám, app nabobtná klidně i tak, že sežere veškerou RAM.

J.P.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Je to docela zvláštní jelikož jsem napsal již několik produkčně využívaných aplikací ve WPF (jedna z nich reporting nástroj) a s podobným chováním jsem se nikdy nesetkal. Respektive nikdy aplikace "nežrala" tolik paměti, aby bylo třeba hledat proč to tak je. Tudíž bych si nebyl jist, že to je na 100% tak , jak říkáte. I když určitě značná část pravdy tam samozřejmě je.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Dobrý den,

není pochyb, že tento problém ve WPF existuje. Problém se mi podařilo nasimulovat. V tuto chvíli neznám řešení, pouze jsem narazil na různé druhy workaroundů.

Updated: Napsal jsem zde, že jsem neviděl problém v udržování instancí hodnot vlastností v paměti. Spletl jsem se, protože jsem měl špatně nastavené profilování. Proto jsem tuto větu odstranil.

Workarounds:

1) Recyklujte okno, nevytvářejte jej znovu a znovu.

2) Vytvářejte náročná okna ve vlastním vlákně (s vlastním Dispatcherem). Například takto jsem se problémů zbavil:

            for (int i = 0; i < 10000; i++)
            {
                Thread thread = new Thread(() =>
                {
                    var w = new Window1();
                    w.Show();
                    w.Closed += (s1, e1) => w.Dispatcher.InvokeShutdown();
                    w.Close();
                });
                thread.SetApartmentState(ApartmentState.STA);
                thread.Start();
                thread.Join();
            }
nahlásit spamnahlásit spam 0 odpovědětodpovědět

Update 2:

Podle profilování je bug v třídě InputManager, která z nějakého důvodu odesílá události do Dispatcheru, které se podle všeho nehodlají nikdy vykonat a tak si drží referenci a pamět roste. I z toho důvodu pomáhá vytváře pro takový typ formulářů vlastní thread s vlastním Dispatcherem (viz minulý příspěvek).

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

Zlaté Windows Forms... Tam není potřeba řešit hovadiny, ale pouze se plně soustředit na vývoj UI.

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

Zlatý Basic na Didaktiku...

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

Vážení kolegové,

tak si sypu popel na hlavu. Chyba byla opět mezi židlí a klávesnicí. Ovšem přijít na ni se mi podařilo až nyní. Memory leak byl způsoben použitím třídy System.Timers.Timer - ta je IDisposable :-) - tuto jsem použil ve vlastní třídě, která IDisposable nebyla. Zajímavé je, že naprosto stejnou třídu užívám ve winforms a tam se problém za 5 let nasazení v produkčním prostředí neprojevil.

Poznatek:

1. GC.Collect zavolaný ihned po zavření okna většinou nic neprovede, musí se volat v extra threadu aby čistil paměť.

2. GC se chová velmi nenažraně a pokud se opravdu s aplikací pracuje tak GC moc paměť neuvolňuje a spíše požírá čím dál více nové.

3. Pokud použiji výše uvedený timer, dám AddHandler, tak wpf okno zůstává po zavření nadále v paměti. Řešením je RemoveHandler nebo třídu obsahující timer zabalit do IDisposable (poté si WPF odstraní i ten handler). Pokud ovšem zůstává reference na nějaký disposable objekt v okně, nebo podřízené komponentě, GC ponechává v paměti veškeré komponenty a nastavení okna, proto paměť velmi rychle narůstá a neuvolňuje se.

Velmi se Vám zde omlouvám za nevědomost, kterou sem jistě část zdejších uživatelů odradil od tak parádní technologie jako je WPF.

S přáním krásného dne

Jelínek Petr

www.jelineksoft.net

nahlásit spamnahlásit spam 0 odpovědětodpovědět

1) Pochopitelně, GC uvolňuje paměť podle vlastního uvážení, pouštět to ve vlastním vlákně postrádá smysl.

2) GC je udělán myslím dost dobře, ale pokud je program napsán prasácky, nepomůže ani svěcená voda.

3) To jsou ovšem základní nedostatky ve znalostech správy paměti .NET aplikací...

nahlásit spamnahlásit spam 0 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