Grafikus C# – Windows Forms tanfolyam › 13.9 lecke Windows Forms · 13.9 lecke · erőforrások A program lefut, a tesztek zöldek, mégis: egy óra használat után lassú, a memóriahasználat a duplájára nőtt, a rajzolás akadozik, és végül „Out of memory” vagy „Error creating window handle” hibával leáll. Ezek az erőforrás-szivárgás és a teljesítményhibák jellemző tünetei, és gyakran csak hosszabb használat után jelentkeznek. A .NET szemétgyűjtője (garbage collector) a memória nagy részét rendben tartja, de nem mindent: a nem felügyelt erőforrásokat (képek, ecsetek, fájlkezelők, kapcsolatok) neked kell felszabadítanod, és a hivatkozásokat (esemény-feliratkozás, statikus lista) neked kell elengedned. Ebben a leckében megtanulod az Erőforrás-kezelés és teljesítmény
IDisposable és a using helyes használatát, a leggyakoribb szivárgásokat, a mérési módszereket (mérj, ne találgass), és a felület gyorsításának alapvető fogásait.
A lecke végére
- megkülönbözteted a felügyelt (GC) és a nem felügyelt erőforrásokat;
- helyesen használod az
IDisposable-t és ausingblokkot (Bitmap,Graphics,Font,Pen, stream, kapcsolat); - felismered a tipikus szivárgásokat: felszabadítatlan kép, esemény-feliratkozás, statikus gyűjtemény, nyitva maradt Form;
- méred a memóriát és a sebességet (Feladatkezelő, Diagnostic Tools,
Stopwatch); - gyorsítod a felületet (
BeginUpdate,SuspendLayout,DoubleBuffered,VirtualMode); - jobb algoritmust és adatszerkezetet választasz (
StringBuilder,HashSet,Dictionary).
Mielőtt elkezded
Ehhez a leckéhez a 9.8 lecke (képek felszabadítása, Dispose), a 11. fejezet (háttérmunka) és a 13.2–13.3 lecke (hibakereső) szükséges.
Felügyelt és nem felügyelt erőforrások, IDisposable
| Erőforrás | Ki szabadítja fel? | Példa |
|---|---|---|
| Felügyelt memória | A szemétgyűjtő (GC), amikor már nincs rá hivatkozás. | string, List<T>, saját osztályok. |
| Nem felügyelt erőforrás | Te (Dispose/using); a GC csak későn és bizonytalanul. | Bitmap, Graphics, Font, Pen, Brush, FileStream, adatbázis-kapcsolat, Timer, Form (ShowDialog után). |
| Hivatkozás, amely életben tart valamit | Te (leiratkozás, törlés a gyűjteményből). | Esemény-feliratkozás, static lista, gyorsítótár. |
// ROSSZ: a Bitmap és a Graphics sosem szabadul fel (GDI-objektumok: korlátos számban léteznek!)
private Bitmap Bélyegkép(string ut)
{
Image eredeti = Image.FromFile(ut); // a fájl zárolva marad, amíg fel nem szabadítod
Bitmap kicsi = new Bitmap(eredeti, new Size(100, 100));
return kicsi; // az eredeti sosem szabadul fel
}
// JÓ: using: hiba esetén is felszabadít (try–finally)
private Bitmap Bélyegkép(string ut)
{
using (Image eredeti = Image.FromFile(ut))
{
return new Bitmap(eredeti, new Size(100, 100)); // az eredetit a using zárja; a visszaadott kicsit a HÍVÓ szabadítja fel
}
}
// rajzoláskor: a saját Pen, Brush, Font felszabadítása
private void panel_Paint(object sender, PaintEventArgs e)
{
using (Pen toll = new Pen(Color.Purple, 2))
using (SolidBrush ecset = new SolidBrush(Color.Lavender))
{
e.Graphics.FillRectangle(ecset, 10, 10, 100, 50);
e.Graphics.DrawRectangle(toll, 10, 10, 100, 50);
} // e.Graphics-ot NEM te szabadítod fel: a Paint kapja, a keretrendszer kezeli
}
// SAJÁT IDisposable osztály
public class Kepkezelo : IDisposable
{
private Bitmap kep;
private bool felszabaditva = false;
public void Betolt(string ut)
{
Bitmap uj = new Bitmap(ut);
if (kep != null) { kep.Dispose(); } // a régi kép felszabadítása CSERE előtt
kep = uj;
}
public void Dispose()
{
if (felszabaditva) { return; }
if (kep != null) { kep.Dispose(); kep = null; }
felszabaditva = true;
}
}Dispose() azonnal felszabadítja az erőforrást (nem várja a GC-t). Ha egy osztály IDisposable, mindig usingba tedd, vagy hívd a Dispose()t.using (var x = ...) { } a blokk végén (és hiba esetén is) meghívja a Dispose()-t (try–finally, 13.4 lecke). Ha az objektum a blokkon túl is kell (visszaadod), ne usingba tedd: a hívó felelős a felszabadításért.Bitmap mögött operációs rendszer szintű GDI-objektum áll, amelynek a száma folyamatonként korlátos (alapból 10 000). A GC csak később és bizonytalan időben fut: addig több ezer felszabadítatlan Bitmap gyűlhet fel, és kifogysz a GDI-objektumokból, mielőtt a memória elfogyna.Dispose), különben minden csere egy szivárgás. A PictureBox.Image cseréjénél is (9.8 lecke).Jósolj!
A program minden egérmozgásnál (MouseMove) új Pen objektumot hoz létre rajzoláshoz, de nem szabadítja fel. Néhány perc rajzolás után mi történik?
Jóslat ellenőrzése
A GDI-objektumok száma folyamatosan nő (a Feladatkezelő GDI objects oszlopában követhető), míg el nem éri a 10 000-es korlátot: ekkor a rajzolás leáll vagy „Parameter is not valid” / „Out of memory” hiba jelentkezik. A GC közben lehet, hogy felszabadít egyeseket, de nem megbízhatóan. Megoldás: using, vagy a Pen egyszeri létrehozása mezőként, és a Form zárásakor felszabadítása.
A tipikus szivárgások
// 1. ESEMÉNY-FELIRATKOZÁS: a hosszú életű kiadó életben tartja a feliratkozót
public static class Esemenyek { public static event EventHandler AdatValtozott; } // statikus: az egész program élete alatt él
public partial class RiportForm : Form
{
public RiportForm()
{
InitializeComponent();
Esemenyek.AdatValtozott += Frissit; // ✗ a RiportForm bezárása után is él: a statikus esemény tartja!
}
private void RiportForm_FormClosed(object sender, FormClosedEventArgs e)
{
Esemenyek.AdatValtozott -= Frissit; // ✓ leiratkozás: a Form felszabadulhat
}
}
// 2. STATIKUS GYŰJTEMÉNY / GYORSÍTÓTÁR korlát nélkül
public static class Gyorsitotar
{
public static readonly Dictionary<string, Bitmap> Kepek = new Dictionary<string, Bitmap>(); // ✗ csak nő, soha nem ürül
}
// 3. FELSZABADÍTATLAN FORM (ShowDialog után)
private void btnNyit_Click(object sender, EventArgs e)
{
BeallitasForm f = new BeallitasForm(); // ✗ nincs using / Dispose
f.ShowDialog();
}
// 4. NEM LEÁLLÍTOTT TIMER, nem felszabadított stream, nyitott kapcsolat: lásd 11.1, 13.6 leckea.Esemeny += b.Kezelo azt jelenti: az a objektum hivatkozást tart a b-re. Ha az a hosszú életű (statikus, főablak, szolgáltatás), a b (egy már bezárt Form) nem szabadulhat fel a GC számára: a Form és minden vezérlője a memóriában marad. Szabály: a feliratkozó leiratkozik, amikor már nem kell.ObjectDisposedException), vagy többszörös végrehajtás.static szótár sosem ürül, ha te nem ürítesz: korlátozott méretű gyorsítótár (legutóbb használt N elem), vagy WeakReference. Képeket tartó gyorsítótárnál a kivett képeket Dispose-olni kell.Show() alapú ablak bezáráskor megsemmisül; a ShowDialog() ablak nem (12.1 lecke): using vagy Dispose() kell. A Form kódból létrehozott erőforrásait (Pen, Bitmap mezők, saját Timer) a FormClosed vagy a Dispose felülírása szabadítsa fel.Jósolj!
A főablak szolgáltatása public event EventHandler Valtozott eseményt tart. Minden Riport ablak megnyitáskor feliratkozik, de nem iratkozik le. A felhasználó 200 riportablakot nyit és zár be. Hány RiportForm él a memóriában, és mit tesz egy új adatváltozás?
Jóslat ellenőrzése
Mind a 200 él (a szolgáltatás esemény-listája tartja őket), a memóriát és a GDI-objektumokat foglalják, és minden adatváltozásnál mind a 200 kezelő lefut a bezárt ablakokon (lassulás, ObjectDisposedException). Leiratkozás a FormClosed-ben megoldja.
Mérés: mérj, ne találgass
Eszközök
- Feladatkezelő (Ctrl+Shift+Esc) → Részletek fül → oszlopok hozzáadása: Memória (privát munkakészlet), GDI-objektumok, Fogantyúk, Szálak: a program erőforrás-használata időben.
- Visual Studio Diagnostic Tools (Debug → Windows → Show Diagnostic Tools): CPU- és memóriagörbe, Memory Usage → Take Snapshot: két pillanatkép összehasonlítása megmutatja, mely objektumok szaporodtak.
- Stopwatch: egy művelet pontos ideje (13.7 lecke).
- GC.GetTotalMemory(true) és
Process.GetCurrentProcess().WorkingSet64: kódból olvasható memóriaérték. - BenchmarkDotNet: pontos mikro-mérésekhez (haladó; külön NuGet-csomag).
Mérési szabályok
- Release fordításban mérj (a Debug lassabb, optimalizálatlan).
- Ismételj: az első futás lassabb (JIT, gyorsítótár): a mérést többször végezd, és a mediánt nézd.
- Előtte–utána: mérj a módosítás előtt is: különben nem tudod, javult-e.
- Valósághű adat: 10 elemmel minden gyors; mérj a valós mérettel (10 000, 1 millió).
- Egy módosítás egyszerre: különben nem tudod, melyik segített.
- A szűk keresztmetszetet keresd: a program ideje általában kevés helyen megy el; azt gyorsítsd, ne mindent.
public static string MemoriaAllapot()
{
Process p = Process.GetCurrentProcess();
long gc = GC.GetTotalMemory(false) / 1024 / 1024; // a felügyelt halom (MB); a true kényszeríti a gyűjtést mérés előtt
return "Munkakészlet: " + p.WorkingSet64 / 1024 / 1024 + " MB, felügyelt halom: " + gc + " MB, fogantyúk: " + p.HandleCount;
}
// mérés ismétléssel: medián
public static long MedianMs(Action muvelet, int ismetles = 7)
{
List<long> idok = new List<long>();
muvelet(); // bemelegítés (az első futást eldobjuk)
for (int i = 0; i < ismetles; i++)
{
Stopwatch sw = Stopwatch.StartNew();
muvelet();
sw.Stop();
idok.Add(sw.ElapsedMilliseconds);
}
idok.Sort();
return idok[idok.Count / 2];
}Jósolj!
Egy művelet első futása 340 ms, a következő hat 45–52 ms. Melyik érték jellemzi a művelet sebességét?
Jóslat ellenőrzése
Az első futás a JIT-fordítás és a hideg gyorsítótár miatt lassabb; a művelet „állandó” sebességét a következők mutatják (kb. 48 ms, a medián). A felhasználó mégis az első futást érzi (indulás utáni első kattintás): ezért a bemelegítő futás külön adat (és lassú indulásnál érdemes a kritikus kódot indulás után háttérben előmelegíteni).
Gyorsítás: felület, adatszerkezet, algoritmus
// 1. FELÜLET: sok elem hozzáadása egyszerre (a vezérlő minden Add után újrarajzolna)
lstNagy.BeginUpdate(); // a rajzolás szünetel
for (int i = 0; i < 20000; i++) { lstNagy.Items.Add("Elem " + i); }
lstNagy.EndUpdate(); // egyetlen újrarajzolás
// vagy egyetlen hívás: AddRange
lstNagy.Items.AddRange(szovegek.ToArray());
// 2. ELRENDEZÉS: sok vezérlő létrehozásakor
pnl.SuspendLayout();
// ... 200 vezérlő hozzáadása ...
pnl.ResumeLayout(true);
// 3. VIRTUÁLIS MÓD: több százezer sor: csak a látható sorokat kéri el
lv.VirtualMode = true;
lv.VirtualListSize = adatok.Count;
lv.RetrieveVirtualItem += (s, e) => e.Item = new ListViewItem(adatok[e.ItemIndex]);
// 4. VILLOGÁS ellen: DoubleBuffered (saját rajzolt panelhez; 9.3 lecke)
typeof(Panel).InvokeMember("DoubleBuffered", BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic, null, pnlRajz, new object[] { true });
// 5. STRINGEK: összefűzés ciklusban (minden + új szöveget hoz létre: O(n²))
string s = "";
for (int i = 0; i < 50000; i++) { s += i + ","; } // ✗ lassú, sok szemét
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 50000; i++) { sb.Append(i).Append(','); } // ✓ gyors
string eredmeny = sb.ToString();
// 6. ADATSZERKEZET: keresés listában (O(n)) vagy halmazban (O(1))
bool van = nagyLista.Contains(kulcs); // ✗ minden elemet végignéz
HashSet<string> halmaz = new HashSet<string>(nagyLista);
bool vanGyorsan = halmaz.Contains(kulcs); // ✓ szinte azonnalListBox, ListView, TreeView minden elem hozzáadása után újrarajzolná magát. A BeginUpdate és EndUpdate közt szünetel a rajzolás: 20 000 elemnél a különbség akár másodpercek és századmásodpercek közt. Ezt a mérés megmutatja.ListViewItem-et: a vezérlő csak a látható sorokat kéri el (RetrieveVirtualItem). A memóriahasználat és az indulás ideje nagyságrendekkel csökken.HashSet/Dictionary szinte azonnal (O(1)). Egy ciklusban, 100 000 keresésnél a különbség percek és századmásodpercek közt. A string-összefűzés ciklusban O(n²): a StringBuilder O(n).Jósolj!
Egy ciklusban 100 000 szót ellenőrzöl egy 50 000 szavas List<string> szótárban (Contains). Hány összehasonlítást végez a program nagyságrendileg, és mit javasolsz?
Jóslat ellenőrzése
Átlag 100 000 × 25 000 = kb. 2,5 milliárd összehasonlítás (másodpercek–percek). Javaslat: a szótárat egyszer HashSet<string>-be tenni (new HashSet<string>(lista, StringComparer.OrdinalIgnoreCase)): akkor 100 000 közel azonnali keresés. Mérd meg előtte és utána.
Mit tegyél ide: a Feladatkezelő (GDI-objektumok, memória) a szivárgás-vadász futtatása közben, és a Diagnostic Tools memória-pillanatképek összehasonlítása.Javasolt fájlnév: winforms-13-9-meres.webp · Alt szöveg: A memóriahasználat mérése a Feladatkezelővel
Hibakeresés: te vagy a hibadetektív
Mi a baj, és hogyan javítod?
Gondold végig az okot, és csak utána nyisd le a megoldást.
1. „Parameter is not valid” rajzolás közben
Hosszú rajzolás után a program kivételt dob a Kifogytál a GDI-objektumokból (felszabadítatlan Graphics hívásoknál.Ok és javítás
Pen, Brush, Bitmap). using blokk minden rajzolóobjektumra; a Feladatkezelőben figyeld a GDI-objektumok számát.
2. A memória nő, amikor ablakot nyitok-zárok
Minden riport ablak megnyitása után emelkedik a memóriahasználat. Az ablak feliratkozott egy hosszú életű eseményre, és nem iratkozik le. Ok és javítás
-= a FormClosed-ben; a Diagnostic Tools pillanatképei mutatják a RiportForm példányok számát.
3. A kép fájlja nem törölhető
pb.Image = Image.FromFile(ut);
File.Delete(ut); // IOException: a fájlt másik folyamat használja (saját magad)Hiba és javítás
Az Image.FromFile zárolja a fájlt, amíg a kép él. Másold memóriába: using (var tmp = Image.FromFile(ut)) { pb.Image = new Bitmap(tmp); }; a régi pb.Image-et felszabadítod.
4. A lista feltöltése másodpercekig tart
foreach (var s in szovegek) { lst.Items.Add(s); } // 30 000 elemHiba és javítás
BeginUpdate/EndUpdate és AddRange, vagy virtuális mód. Mérd az előtte–utána időt.
5. A keresés lassú nagy adatnál
10 000 elemnél gyors, 500 000-nél percekig tart. Négyzetes (O(n²)) algoritmus: egy ciklusban Ok és javítás
List.Contains vagy IndexOf. Használj HashSet/Dictionary szerkezetet; mérd a nagy adaton.
6. A GC.Collect() „megoldás”
GC.Collect(); // minden metódus végén, „hogy ne szivárogjon”Hiba és javítás
A GC.Collect() nem szabadítja fel azt, amire még van hivatkozás (esemény, statikus lista), és lassítja a programot. A szivárgást a hivatkozás elengedése és a Dispose oldja meg; a GC.Collect() legfeljebb mérés előtt (a true paraméterrel) használható.
Mini projekt: szivárgás-vadász és gyorsító
Kapsz egy kis programot három rejtett problémával (szivárgás és lassúság). Mérd meg, javítsd, és dokumentáld az előtte–utána különbséget:
- Felület:
btnKepek(100 bélyegkép készítése),btnRiport(Riport ablak),btnLista(Lista feltöltése (20 000)),btnSzoveg(Szöveg összefűzése (50 000)),btnKeres(Szótárkeresés),lblMeres(memória és idő),lstNaplo,Timer(1 mp-es memória-kijelzés). - Kiindulás: másold be a hibás kódot (lent); egy mappában legyen legalább 5 nagyobb kép (1–3 MP).
- Mérés előtte: a Feladatkezelőben kövesd (Memória, GDI-objektumok, Fogantyúk): kattints 10-szer a 100 bélyegképre, majd 10 riport ablakot nyiss és zárj be; jegyezd fel az értékeket (táblázat: kezdet, 10 kattintás után).
- Diagnostic Tools: a riportok megnyitása-zárása előtt és után készíts memória-pillanatképet, és hasonlítsd össze: hány
RiportFormél? - 1. javítás (képek):
usingazImage.FromFileés mindenBitmapköré; a régi bélyegképek felszabadítása cserekor; mérd újra a GDI-objektumok számát. - 2. javítás (esemény): a
RiportFormaFormClosed-ben leiratkozik a statikus eseményről; mérd újra (a pillanatképekben nincs régiRiportForm). - 3. javítás (lista, szöveg, keresés):
BeginUpdate/AddRange,StringBuilder,HashSet; mérd az idejét aMedianMssegédmetódussal, Release fordításban. - Jelentés: táblázat: probléma, mérés előtte, mérés utána, mit javítottam, tanulság (legalább 5 sor, számokkal).
- Teszteld: a javítás után a program ugyanúgy működik-e (regresszió: a bélyegképek helyesek, a riport frissül, a lista tartalma azonos); hosszú használat (100 ablaknyitás) után a memória stabil-e.
A hibás és a javított kód (próbáld ki előbb önállóan!)
private readonly List<Bitmap> bélyegek = new List<Bitmap>();
public static class Esemenyek { public static event EventHandler AdatValtozott; public static void Jelez() { if (AdatValtozott != null) { AdatValtozott(null, EventArgs.Empty); } } }
private void btnKepek_Click(object sender, EventArgs e)
{
foreach (string ut in Directory.GetFiles(kepMappa, "*.jpg"))
{
for (int i = 0; i < 20; i++)
{
Image eredeti = Image.FromFile(ut); // nincs using: zárolt fájl, szivárgás
bélyegek.Add(new Bitmap(eredeti, new Size(120, 90))); // a lista csak nő; a régiek nem szabadulnak
}
}
}
private void btnRiport_Click(object sender, EventArgs e)
{
RiportForm f = new RiportForm(); // ShowDialog után nincs Dispose
f.ShowDialog();
}
// RiportForm:
public RiportForm() { InitializeComponent(); Esemenyek.AdatValtozott += Frissit; } // nincs leiratkozás
private void btnLista_Click(object sender, EventArgs e)
{
lstNagy.Items.Clear();
for (int i = 0; i < 20000; i++) { lstNagy.Items.Add("Elem " + i); } // minden Add újrarajzol
}
private void btnSzoveg_Click(object sender, EventArgs e)
{
string s = "";
for (int i = 0; i < 50000; i++) { s += i + ","; }
lblMeres.Text = "Hossz: " + s.Length;
}
private void btnKeres_Click(object sender, EventArgs e)
{
List<string> szotar = Enumerable.Range(0, 50000).Select(i => "szo" + i).ToList();
int talalat = 0;
for (int i = 0; i < 20000; i++) { if (szotar.Contains("szo" + (i * 7))) { talalat++; } }
lblMeres.Text = "Találat: " + talalat;
}A javított változat az alábbi megoldás; előbb mérj és javíts magad.
A javított kód
private readonly List<Bitmap> bélyegek = new List<Bitmap>();
private void btnKepek_Click(object sender, EventArgs e)
{
foreach (Bitmap b in bélyegek) { b.Dispose(); } // a régi bélyegek felszabadítása
bélyegek.Clear();
foreach (string ut in Directory.GetFiles(kepMappa, "*.jpg"))
{
// a fájlt EGYSZER olvassuk be; a bélyeget abból készítjük (nem 20-szor); using zárja az eredetit
using (Image eredeti = Image.FromFile(ut))
{
for (int i = 0; i < 20; i++) { bélyegek.Add(new Bitmap(eredeti, new Size(120, 90))); }
}
}
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
foreach (Bitmap b in bélyegek) { b.Dispose(); } // a Form zárásakor is
base.OnFormClosed(e);
}
private void btnRiport_Click(object sender, EventArgs e)
{
using (RiportForm f = new RiportForm()) { f.ShowDialog(this); } // using: ShowDialog után Dispose
}
// RiportForm:
public RiportForm() { InitializeComponent(); Esemenyek.AdatValtozott += Frissit; FormClosed += (s, e) => Esemenyek.AdatValtozott -= Frissit; } // leiratkozás
private void btnLista_Click(object sender, EventArgs e)
{
lstNagy.BeginUpdate();
lstNagy.Items.Clear();
lstNagy.Items.AddRange(Enumerable.Range(0, 20000).Select(i => (object)("Elem " + i)).ToArray());
lstNagy.EndUpdate();
}
private void btnSzoveg_Click(object sender, EventArgs e)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 50000; i++) { sb.Append(i).Append(','); }
lblMeres.Text = "Hossz: " + sb.Length;
}
private void btnKeres_Click(object sender, EventArgs e)
{
HashSet<string> szotar = new HashSet<string>(Enumerable.Range(0, 50000).Select(i => "szo" + i));
int talalat = 0;
for (int i = 0; i < 20000; i++) { if (szotar.Contains("szo" + (i * 7))) { talalat++; } }
lblMeres.Text = "Találat: " + talalat;
}Mit tegyél ide: a jelentés táblázata (előtte–utána értékek) és a Feladatkezelő a javítás után.Javasolt fájlnév: winforms-13-9-kesz.webp · Alt szöveg: A szivárgás-vadász jelentése
Három önálló gyakorlófeladat
Egyszerre csak egyet csinálj: tervezd meg, írd meg, futtasd, ellenőrizd, és csak utána nyisd ki a megoldást.
1. A saját projekted
Nyiss meg egy korábbi projektet, és keresd meg: hol van Keresd a Bitmap, Graphics, Pen felszabadítás nélkül?Egy lehetséges megoldás
new Bitmap, new Pen, new SolidBrush, Image.FromFile sorokat (Ctrl+Shift+F). Mindegyiknél: van using, Dispose, vagy a felszabadítás a Form zárásakor? A rajzoló objektumokat vagy using alá, vagy mezőbe tedd, és zárjad a Formnál.
2. Gyorsítótár korláttal
Írj képgyorsítótárat, amely legfeljebb 20 képet tart, és a legrégebben használtat felszabadítja. Egy Egy lehetséges megoldás
Dictionary<string, Bitmap> és egy LinkedList<string> a használat sorrendjéhez (LRU): elérésnél a kulcs a lista elejére, 20 fölött a lista végéről eltávolítod a kulcsot és Dispose-olod a képet. A gyorsítótár IDisposable, a Form zárásakor mindent felszabadít.
3. Mérés ismétléssel
Mérd meg a Kis elemszámnál a különbség elhanyagolható; a string += és a StringBuilder sebességét 1000, 10 000 és 100 000 elemnél. Mit látsz?Egy lehetséges megoldás
+= ideje négyzetesen nő (10× több elem → kb. 100× lassabb), a StringBuilder lineárisan. Az optimalizálás ott kell, ahol a mérés a szűk keresztmetszetet mutatja.
Gyakorlótár: 6 további feladat
Három szinten gyakorolhatsz: alap, közepes és kihívás. Csak a próbálkozás után nyisd le a megoldást.
Alap szint
1. Ki szabadít fel?
Ki szabadítja fel: (a) egy (a) a GC; (b) te (string; (b) egy Bitmap?Megoldás megnyitása
Dispose/using).
2. using
Mire jó a Hiba esetén is felszabadítja az using?Megoldás megnyitása
IDisposable erőforrást a blokk végén (try–finally).
Közepes szint
3. Szivárgás oka
Miért nem szabadul fel egy bezárt Form, ha egy statikus eseményre feliratkozott? A statikus esemény hivatkozást tart a Form kezelőjére: a GC nem gyűjtheti, amíg van rá hivatkozás. Leiratkozás kell.Megoldás megnyitása
4. Mikor mérj?
Mikor optimalizálj? Ha a mérés valódi problémát mutat (lassú, nő a memória), és a szűk keresztmetszet ismert. Előtte–utána mérés, Release fordításban, valós adatmérettel.Megoldás megnyitása
Kihívás szint
5. WeakReference
Mire jó a Gyenge hivatkozást tart az objektumra: a GC felszabadíthatja, ha kell a memória. Gyorsítótárnál jó, ha az adat újraszámolható; a nem felügyelt erőforrást (WeakReference egy gyorsítótárban?Megoldás megnyitása
Bitmap) tartó elemeket mégis Dispose-olni kell.
6. O(n) és O(1)
Miért gyorsabb a A hash alapján közvetlenül a helyre ugrik (átlag O(1)), míg a HashSet.Contains?Megoldás megnyitása
List.Contains minden elemet végignéz (O(n)).
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. Az operációs rendszer szintű erőforrás (kép, ecset, fájl, kapcsolat), amelyet Folyamatonként kb. 10 000 GDI-objektum létezhet; kifogyva a rajzolás hibás. A hosszú életű kiadó életben tartja a feliratkozót, ha az nem iratkozik le. Feladatkezelővel (memória, GDI, fogantyúk), a Diagnostic Tools pillanatképeivel, Szünetelteti a vezérlő újrarajzolását sok módosítás alatt. Szöveg ciklusban történő építésénél (az összefűzés négyzetesen lassul). Nem: nem szabadít fel hivatkozott objektumot, és lassít. A szivárgást a hivatkozás elengedése és a 1. Mi a nem felügyelt erőforrás?
Dispose-zal kell felszabadítani.2. Miért korlátos a GDI?
3. Hogyan szivárog az esemény?
4. Mivel mérsz memóriát?
GC.GetTotalMemory-mel.5. Mire jó a BeginUpdate?
6. Mikor StringBuilder?
7. Kell-e a GC.Collect()?
Dispose oldja meg.
Mit tudsz már?
- Megkülönbözteted a felügyelt és a nem felügyelt erőforrásokat, és helyesen használod a
using-ot. - Felismered a tipikus szivárgásokat (kép, esemény, statikus gyűjtemény, Form).
- Módszeresen mérsz (Feladatkezelő, Diagnostic Tools,
Stopwatch), előtte–utána. - Gyorsítod a felületet és az algoritmust a mérés alapján.
Következő lecke: Mini projekt: hibás alkalmazás javítása
A 13. fejezet záró projektje: egy hibás, lassú, szivárgó alkalmazás kivizsgálása és javítása a fejezet összes eszközével.



