C# WPF Thread memóriaszivárgás probléma

C# WPF Thread memóriaszivárgás probléma
2022-06-02T23:59:18+02:00
2022-06-11T21:01:32+02:00
2022-12-07T02:25:36+01:00
Papa76
Készítettem, egy "memória szivárogtatót". De nem szándékosan.
Egy "saját" messagebox-ot szeretnék használni, amit egy háttérben, vagy párhuzamosan futó szálból is meg tudnék jeleníteni. Akár többet is egyszerre. A window-ot el is készítettem, de csak új szálként (?STA) tudtam megjelenni.

private void Button_Click_2(object sender, RoutedEventArgs e) { Thread thread = new Thread(new ThreadStart(Fut)); thread.SetApartmentState(ApartmentState.STA); thread.IsBackground = true; thread.Start(); thread.Join(); } void Fut() { Üzenet Üz = new Üzenet(); Üz.bsCímsor = "Címsor"; Üz.bsÜzenet = "fsadfasdga"; Üz.ShowDialog(); }
Ha a gombnyomásra a "Fut()" indul el, és áll le (this.Close()), akkor nincs semmi gond. Vagy csak nem látom.
De ha a fenti módon indítom (és ilyesmire lehet szükségem), akkor minden egyes indítással felhasznál kb + 1(2)MB memóriát. Egészen addig, amíg el nem éri az 512MB-ot, és memória hiányra kivételt dob a program. Már sok mindent kipróbáltam, de nem találom a megoldást. Arra gondolok, hogy a 'thread' -ot nem tudja valamiért felszabadítani a GC. Próbáltam a végére beírni, hogy thread=null, de nem segített. Mit rontok el? Köszi minden infót!
Mutasd a teljes hozzászólást!
Már hogyne lett volna meg a szivárgás oka, végig ott volt/van az orrod előtt.

.net-ben 3 féle memória áll a rendelkezésedre.
1. Stack: ez az a terület amin a value típusok foglalódnak és amit a program futás közben használ.
2. Managed heap: ez az a memória terület ahova az összes new operátorral létrehozott osztálypéldány (referencia típus kerül). Ezt a GC kezeli, nincs sok dolgod vele.

3. Nem managelt heap: ez a windows saját memóriája, melyet nem kezel (nem kezelhet) a GC. Ha ezen a területen foglalsz valamilyen windows recource-ot (fáj, portok, httpclient stb... és a THREAD is) akkor azt neked kell a használat után felszabadítani. Ha nem teszed meg akkor a program futása alatt magától (GC álltal) nem lesz felszabadítva, ergo memory leaked lesz.

A Thread egyébként egy nagyon lowlevel  resource. .net-ben soha nem használjuk. Helyette van a ThreadPool, mely tulajdonképpen a thread-eket wrappeli be  és thread forrásként szolgál. Ha inne kérsz egy szálat akkor azt a .net fogja addni.
Threading objects and features

A .net evolúció során a következő állomás a Task volt mely a ThreadPoolt használja, de használata egyszerűbb.

Task Class (System.Threading.Tasks)

Üdv
E
Mutasd a teljes hozzászólást!

  • OMG ?

    Szerintem kezdjük a probléma legelején. Mit szeretnél csinálni?
    Mutasd a teljes hozzászólást!
  • Próbáltam a végére beírni, hogy thread=null, de nem segített.

    thread.Abort ();

    De ha a fenti módon indítom (és ilyesmire lehet szükségem), akkor minden egyes indítással felhasznál kb + 1(2)MB memóriát. 

    Mert csak új szálat hozol létre, a már meglévők mellé, azoknak pedig memória igényük van, így csak növeled a futó szálak számát, de nincs egyik sem megállítva a fenti megoldással.

    A szálkezelés nem egyszerű dolog, semelyik platformon, még akkor sem, ha fejlesztői eszközökkel rendesen támogatva is van. A szálkezelésnek az alapvető filózófiája nagyon nagy vonalakban az, hogy egy adott szál indítása - megfelelő paraméterek átadásával - után elvégzi a feladatát, és ha az megvan, akkor leállítjuk. Feleslegesen a háttérben nem futtatunk semmitevő szálakat. Ha neked arra van szükséged, hogy egy msg box-ot megjeleníts, akkor indíts rá egy új szálat, jelenítse meg a box-ot, a paraméterrel átadott üzenettel, aztán vagy valamilyen felhasználói iterakcióra, vagy timeout után, de a szál befejeve a műkődését, le kell állítani. És akkor elkerülhető a memória terület elfogyása (memória leak).

    Persze ez csak egy skiccelt leírás, a szálkezelés buktatóiról, és helyes használatáról könyvtárnyi szakirodalom van, és még ezek alapos ismerete mellett is be lehet futni nagyon nehezen felderíthető hibákba.

    Itt található egy példa, szál létrehozására, paraméterrel.
    Mutasd a teljes hozzászólást!
  • Sejtettem hogy egyre több szál fut a háttérben. Csak sajnos képtelen vagyok leállítani. Köszönöm, átnézem a linket is, amit küldtél.

    Megpróbáltam a következőt, de ezzel is ugyanaz az eredmény:
    thread = new Thread(new ThreadStart(Fut)); Debug.WriteLine("Első: "); Debug.WriteLine(thread.IsAlive); thread.SetApartmentState(ApartmentState.STA); thread.IsBackground = true; Debug.WriteLine("Második: "); Debug.WriteLine(thread.IsAlive); thread.Start(); Debug.WriteLine("Start után: "); Debug.WriteLine(thread.IsAlive); thread.Join(); thread.Abort(); Debug.WriteLine("Abort után: "); Debug.WriteLine(thread.IsAlive); Debug.WriteLine(thread.ThreadState);
    Ez alapján nekem olyan, mintha leállna a szál, de a memória mégsem szabadul fel???
    Mutasd a teljes hozzászólást!
    Csatolt állomány
  • Alapvetően egy Window-ot szeretnék megjeleníteni, és várni a válaszára. (Üzenet, hasonló mint a messagebox.)

    Ez a Window (most még) csak egy ok gombot, TextBlock-ot, és egy ProgressBar-t tartalmaz. Egy DispatcherTimer is futna benne, de azt jelenleg teljesen kikommenteztem, hogy ne zavarjon be.

    A programomban lesznek háttérben futó, párhuzamos dolgok is. Amikkel előfordulhat, hogy meg kell jeleníteniük egy-egy üzenetet/kérdést. Ekkor a szálnak várnia kell, a kezelő válaszára. Előfordulhat, hogy egyszerre több szál is megjelenítené ezt az ablakot.
     
    A párhuzamos szálat, - amiből még csak egy van, és ki is van kommentezve - a static System.Timers.Timer-el indítottam el. Lehet, hogy már ez sem jó megoldás? Majd a timer eseménye jeleníti meg az üzenetet. Ezt váltottam ki most egy gombnyomásra a mainwwindowból. Mindkét esetben, csak úgy sikerült hogy a Thread STA-t használtam
    Mutasd a teljes hozzászólást!
  • Itt azt olvasom, hogy az Abort nem garanálja a szál azonnali leállását, hanem csak jelzi a szálról, hogy már nincs rá szükség. Ráadásul egy thread objektum memória felszabadítása sem egyszerű a GC-nek, tehát ebből az következik, hogy a szálat csak terminate-be lehet rakni, annak teljes destroy event-je a rendszertől és a GC lefutásától függ. A fenti linken van egy példakód is a szálak lezárására, azt kellene menézni.
    Mutasd a teljes hozzászólást!
  • Ha szabad kivülállóként beleszólni: esetleg az Üz.ShowDialog() mögé egy Üz.Dispose() ?
    Mutasd a teljes hozzászólást!
  • Egy "saját" messagebox-ot szeretnék használni, amit egy háttérben, vagy párhuzamosan futó szálból is meg tudnék jeleníteni.

    Maradjunk annyiban, hogy nem szeretnél ilyet csinálni. Helyette legyen egy UI szálad ami a teljes UI-t kezeli, beleértve a kis custom message box megjelenítését is, amikor arra szükség van. Background thread akkor kell, ha valami olyasmit akarsz csinálni,
    - ami a UI életciklusának végéig, azzal párhuzamosan kell fusson
    - vagy valamilyen komplex műveletet szeretnél végrehajtani, de azt tapasztalod, hogy befagy a UI.
    Igen valószínűnek tartom, hogy az utóbbi az oka annak, hogy background thread-eket szeretnél alkalmazni - van egy feladat, amit gombnyomásra végre kell hajtani, eltart egy ideig, de ha kész, szeretnéd erről értesíteni a felhasználót. Azt nem tudjuk, hogy a művelet, amit végzel, az I/O vagy CPU bound, ez szintén befolyásolja a megfelelő eszköz választását.

    Tégy magadnak egy szívességet és ismerkedj meg a C# 5-ben bevezetett async-await mintával (más néven a Task-based Asynchronous Pattern). Ha érdekel az is, hogy hol tart a folyamat, akkor jól jöhet még az IProgress minta ismerete is.
    Persze lehet, hogy régebbi .NET Framework verziót kell támogatnod, ami az async-await-hez szükséges eszközöket nem ismeri. A régi időkben az aszinkron (elsősorban I/O bound) műveleteket az Event-based Asynchronous Pattern-nel vagy az Asynchronous Programming Model-lel oldották meg.
    Mutasd a teljes hozzászólást!
  • Erősen támogatom H.Lilla hozzászólását: nem akarsz főszálon kívül UI -t macerálni. Válassz valami más megoldást. Amúgy meg fogj egy profiler -t és nézd meg mi zabálja fel a memoriát.
    Mutasd a teljes hozzászólást!
  • Köszi, igen igazad van, valóban nem szeretnék ilyent csinálni. De nem tudom, hogy megoldható-e máshogy. Egyenlőre csak elképzelésem van az egészről.
    Ez (ezek) a szálak, az UI teljes életciklusában, folyamatosan kell, hogy fussanak. Ráadásul soros portot (többet is - egymástól függetlenül) és SQL-t, és más eszközök kapcsolatát hivatottak kezelni. Egy ilyen szálba tervezem megírni magát a folyamatot is (több is lesz belőle), aminek szintén párhuzamosan, de "függetlenül" kell futnia az UI-tól. Ezeket a szálakat nem indíthatja, és nem zavarhatja meg semmi az UI-ból. De ha "problémát észlel" valamelyik, vagy döntést vár a felhasználótól, akkor meg kellene jelenítenie a custom message box-ot, és várnia a válaszra. A szálak indítását egy Splash Window-al kezdtem el megoldani, ami még az UI-nak szánt MainWindow előtt elindít mindent, vagy ha ez nem sikerül, akkor megjeleníti a hibát, és leállítja a programot, vagy még nem tudom pontosan. Ez a program "magától" működne, a felhasználót szinte csak informálnia kellene.
    Átnézek mindent, amit linkeltél. Nagyon kezdő vagyok még. .Net Framework 4.7.2. fölé (egyenlőre) nem mehetek. A régebbi linkjeidben, mintha azt írták volna, hogy 4-től léteznek a Task-ok. De még rendesen nem tudtam elolvasni.

    Ennek ellenére ezt a memória szivárgást szeretném tudni, hogy mi okozza, főleg hogy hogyan lehet megállapítani.
    Mutasd a teljes hozzászólást!
  • Igen, nekem is jobb lenne, ha nem kellene. De (most) ötletem sincs, hogy hogyan máshogy oldhatnám meg.
    Nagyon köszi. Csak a VisualStudioval próbáltam megnézni, de annyira sok adatot jelenített meg, hogy nem láttam az erdőben a fát. Rákerestem, és utánajárok, sajnos MÉG! kicsit sem ismerem. Jól sejtem hogy ez egy olyan valami, ami kijelzi, hogy a programom melyik része, mennyit foglal a memóriából? Ez nagyon jó dolog lehet.
    Mutasd a teljes hozzászólást!
  • Nem ismeri fel a Üz.Dispose() metódust. Ezt esetleg nekem kellett volna megírnom a windowba?
    Mutasd a teljes hozzászólást!
  • Csináltam egy pici kis progit, ami nagyjából/talán azt tudja amit írsz.

    Elindítod.
    Megnyomod a start gombot.
    Az Output windowban látni fogod a háttérmunkát logk formájában.
    Feldob egy kérdést. Válaszolsz rá.
    Megnyomod az answer gombot.
    Majd  befejezi a munkát.

    No offense de szerintem ennyi tudással nem ártana egy tanárt keríteni.

    Üdv
    E
    Mutasd a teljes hozzászólást!
    Csatolt állomány
  • Vagy nem a legnehezebb softwer architektúrát igénylő problémával kezdeni, peace
    Mutasd a teljes hozzászólást!
  • A ShowDialog-ot te írtad?
    Mutasd a teljes hozzászólást!
  • Nem, a ShowDialog()-ot nem én írtam.
    Erre jutottam az interneten összeszedett infókból, hogy így kell megjeleníteni egy új Window-ot...
    Mutasd a teljes hozzászólást!
  • Talán még jobban átlátnám a programodat, ha fájlként csatolnád egy hozzászóláshoz.
    Mutasd a teljes hozzászólást!
  • Köszi, és igazad van. Ráadásul az éjjel készítettem egy új projectet, amiben csak ez van. (Az eredeti nagy, és sokkal zavarosabb is...) Már arra is gondoltam, hogy valami más zavar be. Ezt most mellékelem is. Nem is tudom, miért nem ezzel kezdtem?. Közben használtam a guglit is (kb 1 hónapja akadt be itt a lemez), ennek a nyomai is benne vannak, de nem leltem rá a megfelelő megoldásra.
    Odáig jutottam a Performance profiler segítségével, amit thot javasolt (de nem értek hozzá) , hogy a window példányai nem szűnnek meg. Gondolom ezért a thread-ot sem lehet felszabadítani. Viszont ha csak a szöveg összeillesztést futtatom, akkor megszűnnek a thread példányai. De ha csak a Windowot nyitom meg, és zárom be, akkor annak a példányai is megszűnnek. Egyik esetben sem látok szivárgást. De ha a threadból indítom a windowot, akkor jön a memória szivárgás, de rendíthetetlenül...
    Mutasd a teljes hozzászólást!
    Csatolt állomány
  • Nagyon köszi, sok és számomra nagyon hasznos infó van benne. Ha jól értelmeztem, akkor ez egy példa az async-await használatára, amit H.Lilla is javasolt. Mindenképpen megfogadom a tanácsaitokat, és elindulok ebbe az irányba. Főleg, mert tetszik is. (Számomra a 20éves SCADA, és PLC/Robot programozás után(közben) a C# kicsit nyakatekert.)
    De ezt a projectet (ami nem lesz kicsi) muszáj új ablakokat megjelenítő megoldással megoldanom. Amit csak nehezít, hogy nem szeretném ugyanazt kétszer megírni, és rengeteg szálból kell majd megjelenítenem egy-egy windowot, "függetlenül" a UI száltól. Ezeknek a szálaknak alapvetően semmi interakciójuk nem lesz az UI-val.
    Igaz, egy tanár, vagy csak egy ismerős aki tud c#-ot programozni tényleg nagy segítség lenne, de addig is marad a gugli, meg a közösségi média.
    Mutasd a teljes hozzászólást!
  • Background process-ek által nyitogatott ablakok ötletét nem tartom jó dolognak.  Képzeld el hogy a paksi atomerőműben a control szoftver ablakokat dobálna... hamar baj lehetne.

    A process-ek kéréseit a felületen listáznám, esetleg prioritást adnék nekik, így a szoftver kezelője láthatja a nyitott kérdéseket. És megfelelő sorrendben válaszolhatna rájuk.
    Mutasd a teljes hozzászólást!
  • Ez jó ötlet. Ezt a megoldást az ipari HMI-kben, SCADA-ban alarmoknak nevezik. Ebből én 3 prioritást használok (piros, sárga, zöld, egységenként csoportosítva, nyugtázási igénnyel, felhasználó loggolással). Ennek a kezelése lesz az egyik process (ha egy elég lesz neki) a programomban. Ahogy említetted az erőművet, gondolom ott is valami SCADA lehet (mi más? ), ami szintén így működik. Ez esetben ne gondolj sok előugró ablakra, mert ahogy írtad is, abból csak baj lenne. Az egyik szál feladata az lesz, hogy figyel egy soros portot, majd a kód olvasótól kapott adatot rögzíti. De ha ismeretlen adatot kap, akkor megjelenik egy window, ami jelez az operátornak. Illetve egy másik esetben, megkérdezi, hogy hibás-e a termék, ha hibás akkor jönnek a további kérdések. Ilyen felugró ablakokat szeretnék készíteni. Ezek nem zavaróak, sőt ezt szeretik. Hasonló, mint a pénztárgép, amikor csippantás után rákérdez, hogy hány darab? Ezek mellett a Mainwindow, és a sok page, csak paramétereket, infókat tartalmaz majd.
    Találtam megoldást:
    [C# - WPF] "The calling thread must be STA"... probléma illetve BackgroundWorker-ből .Xaml hívása (WPF - C#)
    Hogy menyire jó, azt nem tudom, de úgy tűnik, hogy működik.

    Application.Current.Dispatcher.Invoke(() => { Üzenet üzenet = new Üzenet(); üzenet.ShowDialog(); });
    Amennyire utána olvastam, és felfogtam, ez pont erre való.
    Annak ellenére, hogy az alap probléma megoldódott, azt azért sajnálom, hogy nem lett meg a memóriaszivárgás oka. Tartok tőle, hogy nem most futottam bele utoljára.
    Mutasd a teljes hozzászólást!
  • Már hogyne lett volna meg a szivárgás oka, végig ott volt/van az orrod előtt.

    .net-ben 3 féle memória áll a rendelkezésedre.
    1. Stack: ez az a terület amin a value típusok foglalódnak és amit a program futás közben használ.
    2. Managed heap: ez az a memória terület ahova az összes new operátorral létrehozott osztálypéldány (referencia típus kerül). Ezt a GC kezeli, nincs sok dolgod vele.

    3. Nem managelt heap: ez a windows saját memóriája, melyet nem kezel (nem kezelhet) a GC. Ha ezen a területen foglalsz valamilyen windows recource-ot (fáj, portok, httpclient stb... és a THREAD is) akkor azt neked kell a használat után felszabadítani. Ha nem teszed meg akkor a program futása alatt magától (GC álltal) nem lesz felszabadítva, ergo memory leaked lesz.

    A Thread egyébként egy nagyon lowlevel  resource. .net-ben soha nem használjuk. Helyette van a ThreadPool, mely tulajdonképpen a thread-eket wrappeli be  és thread forrásként szolgál. Ha inne kérsz egy szálat akkor azt a .net fogja addni.
    Threading objects and features

    A .net evolúció során a következő állomás a Task volt mely a ThreadPoolt használja, de használata egyszerűbb.

    Task Class (System.Threading.Tasks)

    Üdv
    E
    Mutasd a teljes hozzászólást!
  • Még egy dolog ahol (amelyik osztályon) a Dispose() metódus megjelenik az implementálja a IDisposable interfészt. Aki viszont implementálja az IDisposable interfacet az tuti, hogy windows recourceot használ. Ugyanis a .net-ben ez az interface és hozzá a using keyword pontosan ennek a problémának a kezelésére lett kitalálva.

    IDisposable Interface (System)

    The primary use of this interface is to release unmanaged resources. 

    E
    Mutasd a teljes hozzászólást!
Tetszett amit olvastál? Szeretnél a jövőben is értesülni a hasonló érdekességekről?
abcd