Prog.Hu

C# Memoria gondok. probléma

Keresés
Hírlevél

C# Memoria gondok.

2013-09-06T04:34+02:00
sediqwe1
sediqwe1Prog.Hu
regisztrált tag
nyitotta: sediqwe1, idő: 2012.03.05. 20:12, moderátor: moderator, megoldás elfogadva: 2012.03.10. 18:02
  Értesítés változás esetén Felvétel kedvencekhez Küldés emailben

Kategóriák:Programozási nyelvek » C#

Sorrend:
Időzóna:
Oldalanként:
Oszd meg!
Sziasztok!
Van egy programom, C# ben, Winform, megnyitogat x oldalt, kilvas belőle y linkeket, és az y linkeket egy datagridbe pakolja, ezt a datagridet egy timer megnyitogatja időnként. Nos, 3-400 link megnyitása után elfogy a ram, és kifagy a program.
Itt valami nagyon nem jól lett megírva.
De mi?
A kiherélt kód:


System.Timers.Timer aTimer = new System.Timers.Timer();
        System.Timers.Timer kijelzo = new System.Timers.Timer();
        private System.Threading.Thread thread;
        private System.Threading.Thread autothread;
 aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);

// Elindító gomb megnyomása!
private void button5_Click(object sender, EventArgs e)
        {
            if( dataGridView1.RowCount >0)
            {
            megkeres(10);
            kijelzo.Interval = 1000;
            kijelzo.Enabled = true;
        }}
// Megkeres(10) meghívása (a "10" a teszthez kellett)
//kijelző elindítása (számol + progressbart növel)

 public void megkeres(int mennyi)
        {
                     
                     aTimer.Interval = (Properties.Settings.Default.window * 1000);
                     aTimer.Enabled = true;
                     
                     
        }
// elinditja az aTimer-t ;
private void OnTimedEvent(object source, ElapsedEventArgs e )
        {
           
            this.BeginInvoke((MethodInvoker)delegate
            {
                    progressBar1.Value = 0;
               
            });
            k = k + 1;
           
            if (dataGridView1.RowCount <= 0)
            {
                aTimer.Stop();
                kijelzo.Stop();
            }
               
            else
            {
                if (thread != null)
                {
                    thread.Abort();
                }         
                            thread = new System.Threading.Thread(megnyit);
                            thread.Start();
                            Console.WriteLine("Egyik szál elindult: " + k);
           
         }

        }
// ellenörzi hogy fut e már egy ilyen thread, ha igen, leállítja, ellenörzi hogy van e feldolgozandó cucc a listában

 public void megnyit()
        {
            if (dataGridView1.RowCount > 0)
            {
                string link = dataGridView1.Rows[0].Cells[2].Value.ToString().Trim();
               lement(link);
               
                var methodInvoker = new MethodInvoker(() =>
                {
                    webKitBrowser2.Navigate(link);
                    dataGridView1.Rows.Remove(dataGridView1.Rows[0]);
                   
                });

                Invoke(methodInvoker);
            }
            felszamit();
               
        }
//beolvassa az datagrid 2. cellájában lévő linket a Linkbe elnavigálja a WebKitBrowser-t a linkre, eltávolítja a megnyitott linket a datagridviewből és lementi hogy többé ne legyen beolvasva ...
private void lement(string link)
        {
            sqldolgozo.execute("insert into cimek (link) values('" + link + "')", "collect");
        }
public void felszamit()
        {
                    var methodInvoker2 = new MethodInvoker(() =>
                    {
            label1.Text = "Listában: " + dataGridView1.RowCount.ToString();
                    });
          BeginInvoke(methodInvoker2);
         
                    }
Nos ha jóllátom akkor kb ennyi fele megy a program.
De itt valahol nagyon durván túl töltöm a ramot :( egyszerüen nem jöttem rá miért nem gyilkol rendesen a GC!! 800 megánál kifagyott a program :(

elnézést a nagy és átláthatatlan kódért...
Na, a helyzet a következő.

Bizony, máig van egy csúnya, javítatlan memory leak a gyári WebBrowser control-ban.

Itt egy elég részletes összefoglaló:

How to get around the memory leak in the .NET Webbrowser control?

Létezik egy workaround, ami - bár elvileg nem memóriafelszabadításra szolgál - nekem működni látszik. Ezért érdemes jópár óráig tesztelni, mielőtt kijelentjük, hogy ez segít.

Az előző mintánál maradva, próbáld ki ezzel a kóddal:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace memoriaeveszet
{
    public partial class Form1 : Form
    {
        private string[] linkList = new string[] { "http://www.facebook.com", "http://www.prog.hu", "http://www.msn.com", "http://www.iwiw.hu", "http://www.asp.net" };
        private Random rnd = new Random();

        [DllImport("KERNEL32.DLL", EntryPoint = "SetProcessWorkingSetSize", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        internal static extern bool SetProcessWorkingSetSize(IntPtr pProcess, int dwMinimumWorkingSetSize, int dwMaximumWorkingSetSize);

        [DllImport("KERNEL32.DLL", EntryPoint = "GetCurrentProcess", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        internal static extern IntPtr GetCurrentProcess();

        public Form1()
        {
            InitializeComponent();
            webBrowser1.ScriptErrorsSuppressed = true;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            IntPtr pHandle = GetCurrentProcess();
            SetProcessWorkingSetSize(pHandle, -1, -1);

            int rndVal = rnd.Next(0, linkList.Length - 1);
            webBrowser1.Navigate(linkList[rndVal]);
        }
    }
}

A WebKitBrowser-ről valaki írta, hogy szerinte az nem szivárogtat memóriát, de ez ezek után vagy igaz, vagy nem. Mindenesetre gyanús, hogy a problémádat különböző hibák okozzák, melyek tünete (a memóriaszivárgás) megegyezik. Tehát könnyen lehet, hogy ezen kívül is van szivárgás a programodban. Így én azt javaslom, hogy egyelőre tartsuk talonban a WebKitBrowsert, de ne arra koncentráljunk.

Szóval szerintem próbáld ki ezt, és ha ez nem szivárog több óra (esetleg több nap) után sem, akkor érdemes továbblépni. előzmény

jó lenne az egész kódot látni, mert ez így kevéske előzmény
Mivel ez terheli, az üres "álló" kód nem növeli, úgy gondolom hogy itt van a hiba valahol...vagy valamit láttál ami kimaradt , mint elágazás? előzmény
Ebből a kódból nem derül ki, hogy pl. a timerjeid inicializálását hova tetted. Ha mindig létrehozol egy újat, és nem a meglévőket használod, simán okozhatja ez a gondodat.

Szóval jó lenne a teljes képet látni, hogy segítséget kapj.

Ráadásul ahogy nézem, timerből indítasz n másodpercenként új szálat - nem csoda, hogy telik a memória. Ez egy elég sajátos megoldás.

Pontosan mi a célod? Lehet, hogy úgy könnyebben lehetne valami szerkezeti mintát javasolni, ami működhet. előzmény
Inicializálás:
{
    public partial class Form1 : Form
    {
       
        System.Timers.Timer aTimer = new System.Timers.Timer();
        System.Timers.Timer kijelzo = new System.Timers.Timer();
        private System.Threading.Thread thread;
        private System.Threading.Thread autothread;
       
        public Form1()
        {
A cél amit irtam :) De azért leírom még1x hátha félre, vagy egyáltalán nem érthetőre sikeredett :)
Egy listát szeretnék feldolgozni, amiben facebbok linkek vannak.
A listában lévő linkekete a timer szerint 5-60 ms enként szeretném megnyitni, úgy hogy ez ne zavarja a progit, szal szálban nyilnak megfele, hogy ne legyen sok szál, ha betöltött ha nem a webkitbrowser, kap egy abortot, ezzel gondolom meg is ölöm egyúttal, és újra meghívom azt a szálat. ami megnyitja a webkitbrowserben a linket, törli az adott datagridview sort és terheli tovább a memóriám :) előzmény
Korábbi témád alapján ez a WebKitBrowser valami COM komponens, nem?

Azt nem fogja a GC felszabadítani, vagyis minden alkalommal, amikor indítasz egyet, szépen foglalja a memóriát.

COM objektumot explicit fel kell szabadítani, pl. egy ilyen metódussal:

        private void Release(object o)
        {
            try
            {
                if (o != null)
                    System.Runtime.InteropServices.Marshal.ReleaseComObject(o);
            }

            finally
            {
                o = null;
            }
        }

De miért nem használod a beépített WebBrowser komponenst? Az managed kód, automatikusan felszabadulna. előzmény
Az még brutálisabb módon növelte a memória éhséget, ezért gondoltam hogy a kód többi részével van a gond :\ előzmény
Ugye érted a különbséged a managed és az unmanaged kód között?

Ha másodpercenként létrehozol egy új objektumot az természetes, hogy sok memóriát fog igényelni. Ha sokallod, másképp kell szervezned a programodat.

Amennyiben azonban 100% managed komponenst használsz, a GC előbb-utóbb automatikusan ki fogja takarítani (egészen pontosan akkor, amikor már nincs rá referencia). Ettől még betelhet a memóriád, és akkor össze fog omlani (ha nem kezeled le), de elviekben a dolog működhet (pl. ésszerű mennyiségű WebBrowser-t használsz, nem másodpercenként hozol létre újat).

Azonban ha unmanaged komponenst használsz és azt nem szabadítod fel, akkor csak idő kérdése, hogy biztosan összeomoljon a programod, hiszen csak eszi, eszi, eszi a memóriát.

Ez most a Webkit.NET komponens, amit használsz? Mert az managed, így azzal kevesebb gondod van. Mivel azonban biztosan vannak COM-részei, így használat után hívd meg a Dispose() metódusát, hogy azok is felszabaduljanak. előzmény
összesen 2 webkitbrowser van felpakolva a formra, az egyik állandó tartalommal, a másik váltakozó tartalommal.
a váltakozó is webkitbrowser, abba hívogatom meg a Navigate elemet.
ebből úgy gondolom hogy nem kellene neki terhelődnie, hiszen csak elnavigálok az adott oldalról, ami nem tudom miért is érinti a memória foglalást
hacsak azért nem mert megjegyzi a régi oldalakat, de ennek semmi értelmét nem találom.
előzmény
én úgy gondolom (nulla vagyok objektumorientáltságból(is)) hogy ha egy szálat meghívok, és leabortolom, akkor az megszűnik létezni.
Ha egy webkitbrowserbe meghívok egy új oldalt, a webkitbrowser elfelejti a régit (?) max egy linekt tárol, így annak sem szabad növekednie :\ vagy nem? előzmény
1. Mivel nem látszik a teljes kód, kénytelen vagyok találgatni. Lehet, hogy valamiről azt mondod, hogy szerinted úgy van, ahogy, közben meg teljesen más történik ("nulla vagyok objektumorientáltságból"). Így nehéz egzakt tanácsokat adni.

2. Még mindig nem erősítetted meg, hogy valóban a WebKit .NET komponensről van szó. Feltételezem, hogy arról.

3. Ha biztosan csak egyszer hozod létre, és véletlenül sem többször, akkor igazad van, valószínűleg nem a WebKitBrowserek az oka a szivárgásnak. De mivel ez a WebKitBrowser nagyon is unmanaged komponenseket használ, nincs kizárva, hogy valamilyen módon mégis ettől fogy a memóriád. Egy fél forráskódból ezt nem lehet megmondani.

4. Kezdőként én a helyedben nem nehezíteném a dolgom unmanaged komponensekkel, épp elég problémás lehet a managed kód is (ui. abban is lehet szivárgást okozni). Feltétlenül használod kell ezt a WebKitBrowsert? Próbáld meg a beépített, managed WebBrowser-t használni.

5. Hiába abortálsz egy szálat, az unmanaged erőforrások attól nem szabadulnak fel. A jó ég tudja, mit művel ez a WebKitBrowser, ha úgy használod, ahogy. Elméletileg egyébként igazad van: ha nem hozol létre újat belőle, csak indításkor, és ugyanazt használod, valóban nem kéne a memóriának fogynia, a gyakorlatban meg látod, mi történik. előzmény
webbrowser : 580 mega / 43 megnyitás
webkitbrowser : 116 mega / 43 megnyitás

Ebből indultam ki , hogy a wkb jobb mint a wb...

csak ennyi volt az érv, mivel 120 ból nehezett elérni az összeomláshatárt...semmi másért nem akarom a wkb-t!

gc.collect() lefutására egyébként szabadulgat fel memória, de folyamatosan nő a jószág :(

webkit.net asszem
LINK előzmény
nem értem miért nem másolod ide az egész kódot, talán a NASA-nak csinálsz programot, hogy titkos? Idemásolod és egycsapásra meg fogjuk találni a problémád forrását előzmény
webbrowser : 580 mega / 43 megnyitás


Oké, figyelj.

Ha jól szervezed a programodat, annak nem szabad ennie a memóriát.

Itt egy példa: dobj fel egy formra egy WebBrowser control-t, egy Timer-t (tedd mondjuk 3000-re az interval-t, és persze az Enabled-et true-ra) és itt a kód:

    public partial class LinkProcessorForm : Form
    {
        private string[] linkList = new string[] { "http://www.google.com", "http://www.microsoft.com", "http://www.msn.com", "http://www.hotmail.com", "http://www.asp.net" };
        private Random rnd = new Random();

        public LinkProcessorForm()
        {
            InitializeComponent();
            webBrowser1.ScriptErrorsSuppressed = true;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            int rndVal = rnd.Next(0, linkList.Length - 1);
            webBrowser1.Navigate(linkList[rndVal]);
        }
    }

Mint látod, ez annyit csinál, hogy 3 másodpercenként elnavigál egy véletlenszerű oldalra, amit a linkList-ből szed. Igény szerint bővítheted.

Figyeld meg a memóriahasználatot. Időnként felugrik, aztán szépen lecsökken, ahogy a GC dolgozik. A WebBrowser objektumot egyszer hoztuk létre (rádobtuk a form-ra), és csak használjuk. Nem nagyon fog 580 megát foglalni 43 megnyitás.

A te esetedben nem tudom, mit tettél, hogy 580 MB RAM-ot igényel 43 navigáció, de gyanús, hogy a te kódod a rossz - ez összefüggésben lehet azzal, ahogy timerből indítgatsz és leállítasz szálat (ez amúgy is elég nagy hülyeségnek látszik), ráadásul mindezt tetézed egy COM objektum használatával, amellyel amúgy is nagyon gondosan kell eljárni, hogy nehogy memóriaszivárgást okozzon.

Az eredeti problémádhoz visszatérve: a helyedben én teljesen átszervezném a programomat. Felejtsd el ezt a timerből szálat indítgatós/leállítgatós zagyvaságot, és ugyancsak felejtsd el ezt a WebKitBrowser-t. Használj kizárólag gyári .NET komponenseket - a feladat teljes mértékben megvalósítható velük, és azok biztonságosak.

Fogd a formodat, tegyél rá DataGrid-et, WebBrowser-t, ProgressBar-t és egy BackgroundWorker-t. Ne használj timer-t!

BackgroundWorker Basics in C#

Miután kiismerted a BackgroundWorker-t a backgroundWorker1_DoWork eseményben hozz létre egy végtelen ciklust, és az végezze az érdemi munkát. Az navigáljon a kívánt oldalakra, gyűjtse a linkeket, frissítse a DataGrid-edet. A ciklus végén pedig altasd (Thread.Sleep()) annyi ideig, amíg szeretnéd (50-60 sec). Utána kezdi elölről.

Ne állítsd le ezt a szálat! Ez teljesen rossz gyakorlat és nem várt problémákhoz vezet:

Aborting Threads

Tehát ezt felejtsd el! Csak hibákat indukál, és ráadásul nincs rá szükség.

A szálad (BackgroundWorker) csak pörögjön folyamatosan, amíg be nem zárod a programod (később implementálhatsz Cancel-gombot, de az egy másik téma).

A BackgroundWorker-nek ráadásul van egy ReportProgress() metódusa, amellyel mindenféle trükkös szálkezelés technika nélkül tudod átadni a GUI-nak, hogy éppen hol tart. Ugyanakkor mivel a BackgroundWorker-ről akarod frissíteni a GUI-t, muszáj lesz Invoke-ot használnod.

Ha kérdés van, szólj! előzmény
Én csak annyit tennék hozzá, hogy mikor a timer esemény fut (OnTimedEvent) az elején illene leállítani a timert, és avégén újra elindítani, mivel esetleg többször is meghívódik az esemény úgy, hogy még nem ért véget...
private void OnTimedEvent(object source, ElapsedEventArgs e )
{
  aTimer.Stop();
  this.BeginInvoke((MethodInvoker)delegate
  ...
  }
  aTimer.Start();
}
előzmény
Pont arról rizsáztam ennyit, hogy nem timer-rel kéne ezt a problémát megoldani. előzmény
Ez jogos, de ha ő így akarja/tudja megoldani, akkor ez egy alapvetés. Ugyan úgy mint a szálak esetén a szinkronizálás. előzmény
Nem, nem titkos :) csak nem szép :)
hanem randa :)
mellékeltem a form-t előzmény
Form1.cs
Megfigyeltem a memóriavesztést :) és nem vagyok hülye!


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace memoriaeveszet
{
    public partial class Form1 : Form
    {
        private string[] linkList = new string[] { "http://www.facebook.com", "http://www.prog.hu", "http://www.msn.com", "http://www.iwiw.hu", "http://www.asp.net" };
        private Random rnd = new Random();
        public Form1()
        {
            InitializeComponent();
            webBrowser1.ScriptErrorsSuppressed = true;
        }
       
     

        private void timer1_Tick_1(object sender, EventArgs e)
        {

            int rndVal = rnd.Next(0, linkList.Length - 1);
            webBrowser1.Navigate(linkList[rndVal]);
            //MessageBox.Show("de");
        }

     
    }
}

Mellékeltem a képet! előzmény
hatezmegmi.jpg
a gc kitakarította 170 megára, majd összeomlott a program memória hiányra hivatkozván!(4gb lenne erre a célra....) előzmény
Ez kezd érdekes lenni. Mennyi idő/link megnyitása után hízott ekkorára?

Valamint: melyik .NET verziót használod?

Olvastam néhány érdekes dolgot a WebBrowser control memóriaszivárgásáról, holnap kipróbálom. előzmény
A webbrowser vezérlő nem igazi menedzselt kódú vezérlő, csak egy .net-es csomagoló a WebBrowser COM objektum körül. előzmény
Igen, de nem ez a baj önmagában (elvégre látott már a világ rendesen működő COM komponenst), hanem az, hogy a jelek szerint máig (!) bugos.

Ez nemkicsit röhelyes.

Kísérleteznem kell egy kicsit vele. előzmény
Na, a helyzet a következő.

Bizony, máig van egy csúnya, javítatlan memory leak a gyári WebBrowser control-ban.

Itt egy elég részletes összefoglaló:

How to get around the memory leak in the .NET Webbrowser control?

Létezik egy workaround, ami - bár elvileg nem memóriafelszabadításra szolgál - nekem működni látszik. Ezért érdemes jópár óráig tesztelni, mielőtt kijelentjük, hogy ez segít.

Az előző mintánál maradva, próbáld ki ezzel a kóddal:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace memoriaeveszet
{
    public partial class Form1 : Form
    {
        private string[] linkList = new string[] { "http://www.facebook.com", "http://www.prog.hu", "http://www.msn.com", "http://www.iwiw.hu", "http://www.asp.net" };
        private Random rnd = new Random();

        [DllImport("KERNEL32.DLL", EntryPoint = "SetProcessWorkingSetSize", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        internal static extern bool SetProcessWorkingSetSize(IntPtr pProcess, int dwMinimumWorkingSetSize, int dwMaximumWorkingSetSize);

        [DllImport("KERNEL32.DLL", EntryPoint = "GetCurrentProcess", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        internal static extern IntPtr GetCurrentProcess();

        public Form1()
        {
            InitializeComponent();
            webBrowser1.ScriptErrorsSuppressed = true;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            IntPtr pHandle = GetCurrentProcess();
            SetProcessWorkingSetSize(pHandle, -1, -1);

            int rndVal = rnd.Next(0, linkList.Length - 1);
            webBrowser1.Navigate(linkList[rndVal]);
        }
    }
}

A WebKitBrowser-ről valaki írta, hogy szerinte az nem szivárogtat memóriát, de ez ezek után vagy igaz, vagy nem. Mindenesetre gyanús, hogy a problémádat különböző hibák okozzák, melyek tünete (a memóriaszivárgás) megegyezik. Tehát könnyen lehet, hogy ezen kívül is van szivárgás a programodban. Így én azt javaslom, hogy egyelőre tartsuk talonban a WebKitBrowsert, de ne arra koncentráljunk.

Szóval szerintem próbáld ki ezt, és ha ez nem szivárog több óra (esetleg több nap) után sem, akkor érdemes továbblépni. előzmény
Pontosan nem tudom, de percek kérdése se volt
4.0 a .net! előzmény
most pl 71 * futott le és semmi jele a memória zabálásnak.
450 * futott le, és 180 megát foglal , tesztelem a konkrét linkekkel, amik több tartalmat foglal...
lehet hogy a lassú net miatt omlana össze egyébként? előzmény
lehet hogy a lassú net miatt omlana össze egyébként?


Nem valószínű vagy csak közvetett ok lehet.

Most a workaround-verzióval próbálkoztál? Mert az előzmény alapján nem túl egyértelmű. előzmény
nem, mellékeltem is a futó kódot, szal simán :
wb
timer
timer_ticket
előzmény
Értem.

És elolvasod egyáltalán a kisregényeket, amiket írok próbálva megoldani a te problémádat?

Miért nem próbálod ki a workaround-ot? előzmény
Nem nagyon fog 580 megát foglalni 43 megnyitás.

A te esetedben nem tudom, mit tettél, hogy 580 MB RAM-ot igényel 43 navigáció, de gyanús, hogy a te kódod a rossz - ez összefüggésben lehet azzal, ahogy timerből indítgatsz és leállítasz szálat (ez amúgy is elég nagy hülyeségnek látszik), ráadásul mindezt tetézed egy COM objektum használatával, amellyel amúgy is nagyon gondosan kell eljárni, hogy nehogy memóriaszivárgást okozzon.

idáig jutottam, (hétvégén van időm programozni főleg) ezt megdöntöttem azzal hogy "sikerült" valahogy a Te kódoddal összehozni 1.4gigát, összeomlást, mivel ez egy sima timer, így gondoltam a bgw mégrosszabb lesz, de amúgy a timeres dolgon elgondolkodtam, mert a 450 megnyitás -> 120 mega lényegesen kisebb mint az én 43 megnyitás -> 180 megám! Szal azt beemlem a kódba, mindenképpen, úgy mégjobb ez az eredmény, hogy a beépített wb vel érted el (figyelek, csak napközben iskola / gyerek lefoglal ezerrel :( de a 7vége eljött :D) előzmény
Oszd meg másokkal is!