Grafikus C# – Windows Forms tanfolyam › 11.9 lecke

Windows Forms · 11.9 lecke · záró projekt

Mini projekt: folyamatkijelző

A 11. fejezet záró projektjében mindent egy programba építesz, amit az időzítőkről, az állapotjelzésről, a felületi szálról és az aszinkron munkáról tanultál: egy mappa-elemzőt, amely nagy mappafákat is feldolgoz a felület lefagyása nélkül. Kétfázisú folyamatjelzést ad (fájlok keresése, majd feldolgozás), becsli a hátralévő időt, szüneteltethető, megszakítható, időtúllépést is kezel, és a végén kimutatást ad a legnagyobb fájlokról és a kiterjesztésekről.

  • kb. 4–5 óra
  • záró projekt
  • mappa-elemző
Diáklány C# programot készít laptopon

A projekt végére

  • kétfázisú (Marquee, majd százalékos) folyamatjelzést építesz;
  • a munkát Task.Run segítségével a háttérbe teszed, a felületet időzítővel frissíted;
  • szünetet (ManualResetEventSlim) és megszakítást (CancellationToken) valósítasz meg;
  • kezeled a hibákat (hozzáférés nélküli mappák), és részeredményt adsz megszakításkor;
  • a programot a Form bezárásakor is rendben leállítod.

Mielőtt elkezded

Ehhez a projekthez a 11.1–11.8 lecke szükséges, és a 10.1 (Directory, FileInfo), 10.4 (mappaválasztó) lecke.

11.8 CancellationToken →

Követelménytábla (100 pont)

KövetelményPont
Felület: mappa megadása és tallózás, alkönyvtárak jelölő, időtúllépés beállítása, Indítás, Szünet / Folytatás, Mégse; folyamatjelző, állapot-, százalék-, aktuális elem- és időcímkék; eredménylisták, napló; állapotsor.10
Háttérmunka: a mappabejárás és a feldolgozás háttérszálon (Task.Run), a felület közben él; vezérlőt a háttérszál nem ér.15
Kétfázisú jelzés: 1. fázis Marquee + talált fájlok száma; 2. fázis százalékos jelző, 12 / 480 (3%), becsült hátralévő idő (5% és 3 mp után), aktuális fájl rövidítve; a végén Kész.15
Felületfrissítés: az adatokat a háttérszál szálbiztosan teszi közzé (Interlocked), a felületet egy 200 ms-os időzítő frissíti (nincs elárasztott üzenetsor).10
Szünet és folytatás: ManualResetEventSlim; a szünet alatt az eltelt idő megáll; a gombok állapota helyes.10
Megszakítás és időtúllépés: CancellationToken, összekapcsolt forrás, a megszakítás oka kiírva, részeredmény látszik; három állapot (Kesz, Fut/Szunet, Megszakitas).15
Eredmény: összméret, fájlok és mappák száma, a 10 legnagyobb fájl, kiterjesztések szerinti statisztika (darab és méret), a hibák összegzése.10
Hibatűrés: hozzáférés nélküli mappa és fájl kihagyva (nem áll le), a szimbolikus hivatkozás nem okoz végtelen ciklust, nincs kivétel bezáráskor, nem létező mappa érthető üzenet.10
Kód és tesztelés: áttekinthető metódusok és nevek, magyarázó megjegyzések, a tesztlista kitöltve.5

Felületterv

VezérlőNévBeállítás
TextBox, ButtontxtMappa, btnTallozasa mappa útvonala; a gomb a FolderBrowserDialog-ot nyitja (10.4 lecke)
CheckBox, NumericUpDownchkAlkonyvtar, nudIdoalkönyvtárak (alapból bejelölve); időtúllépés mp-ben (0–3600, 0 = nincs)
Button ×3btnIndit, btnSzunet, btnMegseAlt+I, Alt+Sz, Esc gyorsbillentyű; kezdetben a Szünet és a Mégse tiltott
ProgressBarpbAccessibleName = "Mappa feldolgozása"
Label ×4lblAllapot, lblSzazalek, lblMostani, lblIdofázis és állapot; 12 / 480 (3%), még kb. 40 mp; az aktuális elem; eltelt idő
ListBox ×3lstLegnagyobb, lstKiterjesztes, lstNaploa 10 legnagyobb fájl; kiterjesztések; hibák és események
StatusStriptsslUzenetaz utolsó üzenet
TimertmrKijelzoInterval = 200
📷 Képernyőkép helye

Mit tegyél ide: a felületterv vázlata vagy a kész mappa-elemző futás közben.Javasolt fájlnév: winforms-11-9-felulet.webp · Alt szöveg: A mappa-elemző felülete

A 8 lépés támponttal

Haladj lépésről lépésre, és minden lépés után futtasd a programot. A támpont segít elindulni, de a kódot magad írd meg.

1. lépés: felület és állapotok

Helyezd el a vezérlőket, állítsd a gyorsbillentyűket. Írd meg a Beallit(Allapot) metódust (Kesz, Fut, Szunet, Megszakitas), amely a gombokat engedélyezi vagy tiltja. Támpont: 11.8 lecke, állapotkezelés.

2. lépés: a közös adatok

Készíts mezőket a háttérszál és a felület közti adatokhoz: long szamolt, feldolgozott, osszes, osszMeret (Interlocked), volatile int fazis, volatile string aktualis. Egy Eredmeny osztály gyűjti a kimutatást (csak a háttérszál írja, a befejezés után olvassa a felület). Támpont: 11.7 lecke.

3. lépés: az 1. fázis: a fájlok keresése

Egy Stack<string> mappaverem, Directory.EnumerateFiles és EnumerateDirectories; a hozzáférés nélküli mappát (UnauthorizedAccessException) naplózd és hagyd ki; a szimbolikus hivatkozást (FileAttributes.ReparsePoint) ne kövesd. A talált fájlok számát Interlocked.Increment növeli. Támpont: 10.1 lecke.

4. lépés: a 2. fázis: a feldolgozás

Fájlonként FileInfo.Length; a kiterjesztések szótára (darab, méret); a 10 legnagyobb fájl listája (a lista 20 elem fölött rendezve és levágva); a számlálók Interlocked-dal. A hibás fájl (IOException) kihagyva és megszámolva.

5. lépés: Task.Run és a felület frissítése

Az Indítás async void; a bemenetet a felületi szálon olvasod ki; await Task.Run(() => Dolgoz(...)). A 200 ms-os tmrKijelzo olvassa a számlálókat, és frissíti a vezérlőket: az 1. fázisban Marquee, a 2.-ban százalékos jelző és hátralévő idő. Támpont: 11.2 és 11.7 lecke.

6. lépés: szünet és folytatás

Egy ManualResetEventSlim szunet = new ManualResetEventSlim(true); a Szünet Reset(), a Folytatás Set(); a háttérmunka minden fájl előtt szunet.Wait(token) hívást végez (áll, amíg szünet van, de a megszakítás kiszabadítja). A Stopwatch a szünetben megáll.

7. lépés: megszakítás, időtúllépés, bezárás

Indításonként új CancellationTokenSource és egy időzítős, összekapcsolt forrás (CreateLinkedTokenSource); külön OperationCanceledException ág (ki szakította meg?); részeredmény kiírása; a finally felszabadít és nulláz; a FormClosing megszakít és megvár. Támpont: 11.8 lecke.

8. lépés: az eredmény és a finomítás

A befejezés után a 10 legnagyobb fájl és a kiterjesztések kimutatása (méret szerint csökkenően), az összméret emberi formában (1,24 GB), a hibák összegzése. Gyorsbillentyűk, AccessibleName, hibatűrés: nem létező mappa, üres mappa (0 fájl: ne legyen ArgumentOutOfRangeException a pb.Maximum miatt).

Mintamegoldás

Csak akkor nyisd meg, ha már megpróbáltad. A megoldás egyik lehetséges változat: a vezérlőket a Felületterv neveivel kell létrehoznod.

Teljes minta: Form1.cs
Form1.cs (using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks;)C#
private class KiterjStat { public long Db; public long Meret; }

private class Eredmeny
{
public long Fajlok, Mappak, Hibak, OsszMeret;
public Dictionary<string, KiterjStat> Kiterj = new Dictionary<string, KiterjStat>();
public List<KeyValuePair<string, long>> Legnagyobbak = new List<KeyValuePair<string, long>>();
public List<string> HibaSzovegek = new List<string>();
}

private enum Allapot { Kesz, Fut, Szunet, Megszakitas }

private Allapot allapot = Allapot.Kesz;
private CancellationTokenSource forras;
private ManualResetEventSlim szunet = new ManualResetEventSlim(true);
private Stopwatch ora = new Stopwatch();
private bool bezarasra = false;

// a háttérszál és a felület közös adatai
private long szamolt, feldolgozott, osszes, osszMeret;
private volatile int fazis;
private volatile string aktualis = "";

private void Form1_Load(object sender, EventArgs e)
{
tmrKijelzo.Interval = 200;
pb.AccessibleName = "Mappa feldolgozása";
Beallit(Allapot.Kesz);
}

private void Beallit(Allapot a)
{
allapot = a;

btnIndit.Enabled = a == Allapot.Kesz;
btnSzunet.Enabled = a == Allapot.Fut || a == Allapot.Szunet;
btnMegse.Enabled = a == Allapot.Fut || a == Allapot.Szunet;
txtMappa.Enabled = a == Allapot.Kesz;
btnTallozas.Enabled = a == Allapot.Kesz;
chkAlkonyvtar.Enabled = a == Allapot.Kesz;
nudIdo.Enabled = a == Allapot.Kesz;
btnSzunet.Text = a == Allapot.Szunet ? "Folytatás" : "Szünet";
this.UseWaitCursor = a == Allapot.Fut || a == Allapot.Megszakitas;

if (a == Allapot.Megszakitas) { lblAllapot.Text = "Megszakítás folyamatban..."; }
}

private void btnTallozas_Click(object sender, EventArgs e)
{
using (FolderBrowserDialog d = new FolderBrowserDialog())
{
if (Directory.Exists(txtMappa.Text)) { d.SelectedPath = txtMappa.Text; }

if (d.ShowDialog(this) == DialogResult.OK) { txtMappa.Text = d.SelectedPath; }
}
}

private async void btnIndit_Click(object sender, EventArgs e)
{
string mappa = txtMappa.Text.Trim();

if (!Directory.Exists(mappa)) { tsslUzenet.Text = "A megadott mappa nem létezik."; return; }

bool alkonyvtar = chkAlkonyvtar.Checked;
int idoMp = (int)nudIdo.Value;

// minden indításnál nullázunk
Interlocked.Exchange(ref szamolt, 0);
Interlocked.Exchange(ref feldolgozott, 0);
Interlocked.Exchange(ref osszes, 0);
Interlocked.Exchange(ref osszMeret, 0);
aktualis = "";
fazis = 1;
szunet.Set();
lstLegnagyobb.Items.Clear(); lstKiterjesztes.Items.Clear(); lstNaplo.Items.Clear();
lblSzazalek.Text = ""; lblMostani.Text = "";

pb.Style = ProgressBarStyle.Marquee; // 1. fázis: határozatlan
pb.MarqueeAnimationSpeed = 30;
lblAllapot.Text = "Fájlok keresése...";

CancellationTokenSource felhasznalo = new CancellationTokenSource();
CancellationTokenSource ido = idoMp > 0 ? new CancellationTokenSource(TimeSpan.FromSeconds(idoMp)) : new CancellationTokenSource();
CancellationTokenSource egyutt = CancellationTokenSource.CreateLinkedTokenSource(felhasznalo.Token, ido.Token);

forras = felhasznalo;
Eredmeny er = new Eredmeny();
string uzenet;

ora.Restart();
tmrKijelzo.Start();
Beallit(Allapot.Fut);

try
{
CancellationToken token = egyutt.Token;

await Task.Run(() => Dolgoz(mappa, alkonyvtar, er, token), token);

uzenet = "Kész: " + er.Fajlok + " fájl, " + Meret(er.OsszMeret) + ", " + ora.Elapsed.TotalSeconds.ToString("0.0") + " mp.";
}
catch (OperationCanceledException)
{
uzenet = (ido.IsCancellationRequested ? "Időtúllépés (" + idoMp + " mp)" : "Megszakítva") + ": " + er.Fajlok + " fájl feldolgozva.";
}
catch (Exception ex)
{
uzenet = "Hiba: " + ex.Message;
}
finally
{
tmrKijelzo.Stop();
ora.Stop();
egyutt.Dispose(); ido.Dispose(); felhasznalo.Dispose();
forras = null;
}

if (IsDisposed) { return; }

Kijelez(er); // a háttérszál befejezte: az Eredmeny biztonságosan olvasható

pb.MarqueeAnimationSpeed = 0;
pb.Style = ProgressBarStyle.Continuous;
long ossz = Interlocked.Read(ref osszes);
pb.Maximum = (int)Math.Max(1, Math.Min(int.MaxValue, ossz));
pb.Value = (int)Math.Min(pb.Maximum, Interlocked.Read(ref feldolgozott));
lblAllapot.Text = uzenet;
tsslUzenet.Text = uzenet;
lblMostani.Text = "";
fazis = 0;
Beallit(Allapot.Kesz);
}

// ---- a háttérmunka: vezérlőt NEM ér ----
private void Dolgoz(string mappa, bool alkonyvtar, Eredmeny er, CancellationToken token)
{
List<string> fajlok = new List<string>();
Stack<string> verem = new Stack<string>();

verem.Push(mappa);

// 1. fázis: bejárás
while (verem.Count > 0)
{
token.ThrowIfCancellationRequested();
szunet.Wait(token);

string m = verem.Pop();

aktualis = m;

try
{
foreach (string f in Directory.EnumerateFiles(m)) { fajlok.Add(f); Interlocked.Increment(ref szamolt); }

if (alkonyvtar)
{
foreach (string d in Directory.EnumerateDirectories(m))
{
if ((File.GetAttributes(d) & FileAttributes.ReparsePoint) == 0) { verem.Push(d); } // hivatkozást nem követünk
}
}

er.Mappak++;
}
catch (UnauthorizedAccessException) { er.Hibak++; er.HibaSzovegek.Add(m + ": nincs hozzáférés"); }
catch (IOException ex) { er.Hibak++; er.HibaSzovegek.Add(m + ": " + ex.Message); }
}

Interlocked.Exchange(ref osszes, fajlok.Count);
fazis = 2;

// 2. fázis: feldolgozás
foreach (string f in fajlok)
{
token.ThrowIfCancellationRequested();
szunet.Wait(token); // szünetben itt áll; a megszakítás kiszabadítja

aktualis = f;

try
{
long meret = new FileInfo(f).Length;
string kit = Path.GetExtension(f).ToLowerInvariant();

if (kit.Length == 0) { kit = "(nincs)"; }

KiterjStat ks;

if (!er.Kiterj.TryGetValue(kit, out ks)) { ks = new KiterjStat(); er.Kiterj[kit] = ks; }

ks.Db++;
ks.Meret += meret;
er.Fajlok++;
er.OsszMeret += meret;
Interlocked.Add(ref osszMeret, meret);

er.Legnagyobbak.Add(new KeyValuePair<string, long>(f, meret));

if (er.Legnagyobbak.Count > 20)
{
er.Legnagyobbak.Sort((a, b) => b.Value.CompareTo(a.Value));
er.Legnagyobbak.RemoveRange(10, er.Legnagyobbak.Count - 10);
}
}
catch (IOException ex) { er.Hibak++; er.HibaSzovegek.Add(Path.GetFileName(f) + ": " + ex.Message); }
catch (UnauthorizedAccessException) { er.Hibak++; er.HibaSzovegek.Add(Path.GetFileName(f) + ": nincs hozzáférés"); }

Interlocked.Increment(ref feldolgozott);
}
}

// ---- a felület frissítése: 200 ms-onként, a felületi szálon ----
private void tmrKijelzo_Tick(object sender, EventArgs e)
{
lblIdo.Text = ora.Elapsed.ToString(@"mm\:ss");

string ak = aktualis;

lblMostani.Text = ak.Length > 70 ? "..." + ak.Substring(ak.Length - 67) : ak;

if (fazis == 1)
{
lblSzazalek.Text = Interlocked.Read(ref szamolt) + " fájl eddig";
}
else if (fazis == 2)
{
long k = Interlocked.Read(ref feldolgozott), o = Interlocked.Read(ref osszes);

if (pb.Style != ProgressBarStyle.Continuous)
{
pb.MarqueeAnimationSpeed = 0;
pb.Style = ProgressBarStyle.Continuous;
pb.Minimum = 0;
pb.Maximum = (int)Math.Max(1, Math.Min(int.MaxValue, o));
lblAllapot.Text = allapot == Allapot.Szunet ? "Szünet" : "Feldolgozás...";
}

pb.Value = (int)Math.Min(pb.Maximum, k);

double arany = o == 0 ? 1 : (double)k / o;
string szoveg = k + " / " + o + " (" + arany.ToString("P0") + ")";

if (arany >= 0.05 && ora.Elapsed.TotalSeconds >= 3 && k < o)
{
szoveg += ", még kb. " + Hatralevo(TimeSpan.FromSeconds(ora.Elapsed.TotalSeconds / arany - ora.Elapsed.TotalSeconds));
}

lblSzazalek.Text = szoveg;
}
}

private string Hatralevo(TimeSpan t)
{
if (t.TotalSeconds < 5) { return "néhány másodperc"; }
if (t.TotalSeconds < 90) { return Math.Round(t.TotalSeconds / 5) * 5 + " mp"; }

return Math.Round(t.TotalMinutes) + " perc";
}

private string Meret(long b)
{
string[] egyseg = { "B", "KB", "MB", "GB", "TB" };
double v = b;
int i = 0;

while (v >= 1024 && i < egyseg.Length - 1) { v /= 1024; i++; }

return v.ToString(i == 0 ? "0" : "0.##") + " " + egyseg[i];
}

private void Kijelez(Eredmeny er)
{
er.Legnagyobbak.Sort((a, b) => b.Value.CompareTo(a.Value));

foreach (KeyValuePair<string, long> p in er.Legnagyobbak.Take(10)) { lstLegnagyobb.Items.Add(Meret(p.Value) + " " + p.Key); }

foreach (KeyValuePair<string, KiterjStat> p in er.Kiterj.OrderByDescending(x => x.Value.Meret))
{
lstKiterjesztes.Items.Add(p.Key + ": " + p.Value.Db + " db, " + Meret(p.Value.Meret));
}

lstNaplo.Items.Add("Mappák: " + er.Mappak + ", fájlok: " + er.Fajlok + ", összméret: " + Meret(er.OsszMeret) + ", hibák: " + er.Hibak);

foreach (string h in er.HibaSzovegek.Take(200)) { lstNaplo.Items.Add("⚠ " + h); }
}

// ---- szünet, megszakítás, bezárás ----
private void btnSzunet_Click(object sender, EventArgs e)
{
if (allapot == Allapot.Fut)
{
szunet.Reset();
ora.Stop();
Beallit(Allapot.Szunet);
lblAllapot.Text = "Szünet";
}
else if (allapot == Allapot.Szunet)
{
szunet.Set();
ora.Start();
Beallit(Allapot.Fut);
lblAllapot.Text = fazis == 1 ? "Fájlok keresése..." : "Feldolgozás...";
}
}

private void btnMegse_Click(object sender, EventArgs e)
{
Beallit(Allapot.Megszakitas);

if (forras != null) { forras.Cancel(); }
}

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Escape && btnMegse.Enabled) { btnMegse.PerformClick(); return true; }

return base.ProcessCmdKey(ref msg, keyData);
}

private async void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (forras != null && !bezarasra)
{
e.Cancel = true;
bezarasra = true;
forras.Cancel();

while (forras != null) { await Task.Delay(50); }

Close();
}
}

Tesztlista (18 pont)

Minden pontnál próbáld ki, és jegyezd fel az eredményt.

#TesztElvárt eredmény
1Kis mappa (10 fájl) elemzéseGyors befejezés, helyes darabszám és összméret.
2Nagy mappafa (több ezer fájl)A felület közben él (az ablak mozgatható), a jelző halad.
31. fázis kijelzéseMarquee sáv és a talált fájlok növekvő száma.
42. fázis kijelzéseSzázalékos jelző, k / n (p%), aktuális fájl.
5Hátralévő idő5% és 3 mp előtt nincs; utána értelmes, nem ugráló becslés.
6SzünetA feldolgozás és az eltelt idő megáll; a felület él.
7FolytatásOnnan folytatódik; az idő is.
8Mégse az 1. fázisbanLeáll, Megszakítva, a gombok helyesek.
9Mégse a 2. fázisbanRészeredmény látszik, nincs hibaüzenet.
10Mégse szünetbenA megszakítás szünetben is hat.
11Újraindítás megszakítás utánÚj forrás, nulláról indul, helyes számok.
12Időtúllépés (pl. 2 mp)Megáll, az üzenet Időtúllépés, részeredménnyel.
13Hozzáférés nélküli mappa (pl. rendszermappa)Kihagyva, a naplóban szerepel, a feldolgozás folytatódik.
14Nem létező mappaÉrthető üzenet, nincs kivétel.
15Üres mappa0 fájl, nincs ArgumentOutOfRangeException, Kész.
16Bezárás futás közbenMegszakít, megvár, bezár; nincs kivétel, nincs szellemfolyamat.
17Dupla kattintás az Indítás és a Mégse gombonNem indul két feldolgozás; nincs ObjectDisposedException.
18AkadálymentességGyorsbillentyűk működnek, az állapot szövegesen is látszik, a gombok állapota helyes.

Gyakori hibák és önértékelés

Gyakori hibák

  • A háttérszál közvetlenül írja a vezérlőket (cross-thread kivétel).
  • Üres mappánál a pb.Maximum = 0 és Value = 1.
  • A Marquee sáv a végén tovább fut.
  • A megszakítás hibaüzenetként jelenik meg.
  • A régi, megszakított forrás újrahasználata.
  • A hivatkozások követése végtelen ciklust okoz.
  • A felületet minden fájl után frissíted (túlterhelés).

Önértékelés

Pontozd magad a követelménytábla szerint, majd válaszolj:

  • Hol voltál bizonytalan: await, Interlocked, ManualResetEventSlim vagy a token?
  • Mit tennél másképp, ha újra kezdenéd?
  • Miért jobb az időzítős frissítés, mint a soronkénti BeginInvoke?
  • Mi történik, ha a felhasználó szünetben bezárja az ablakot?

Bővítési ötletek

  • Párhuzamos feldolgozás: a fájlok méretének lekérdezése Parallel.ForEach mellett (Interlocked, szálanként külön szótár a végén egyesítve), és a gyorsulás mérése.
  • Szűrők: kiterjesztés, minimális méret, módosítási dátum szerinti szűrés.
  • Exportálás: az eredmény mentése CSV-be vagy JSON-ba (10.5–10.6 lecke), mentési hibakezeléssel.
  • Beállítások: az utolsó mappa, az ablak mérete és a beállítások tárolása (10.8 lecke).
  • Tálca-folyamatjelző: az ablakcímben a százalék (Mappa-elemző – 42%).
  • Fa nézet: a mappák méretének hierarchikus kimutatása (TreeView), a legnagyobb mappák keresése.

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. Emberi méret

Írj metódust, amely a bájtokat KB, MB, GB egységben adja vissza.

Megoldás megnyitása

Ciklusban 1024-gyel osztod, amíg az érték 1024 alá nem esik; az egység tömb indexe nő. Lásd a mintamegoldás Meret metódusát.

2. Két fázis

Miért két fázisú a folyamatjelzés?

Megoldás megnyitása

Az első fázisban az összes fájl száma még ismeretlen (Marquee), a másodikban már ismert, ezért százalékos jelző és becsült idő adható.

Közepes szint

3. Szünet megszakításkor

Szünetben a háttérszál a Wait(token) hívásban áll. Mi történik a Mégse után?

Megoldás megnyitása

A Wait(token) a megszakításkor OperationCanceledException kivételt dob, így a szünetben álló szál is kiszabadul és kilép.

4. Mozgóátlag

Hogyan javítanád a becsült hátralévő idő pontosságát?

Megoldás megnyitása

Az utolsó 5–10 másodperc sebességéből számolsz (a 11.2 lecke mozgóátlaga), így az eltérő méretű fájlok kevésbé zavarják.

Kihívás szint

5. Párhuzamos méretlekérdezés

Hogyan gyorsítanád a 2. fázist több szállal, és mi a kockázat?

Megoldás megnyitása

Parallel.ForEach opciókkal (CancellationToken, MaxDegreeOfParallelism), a számlálók Interlocked-dal, a szótár lock alatt vagy szálanként külön gyűjtve. Kockázat: a lemez (főleg HDD) nem gyorsul a több szállal; a szünet és a megszakítás kezelése összetettebb.

6. Egységteszt

Hogyan tennéd tesztelhetővé a Dolgoz logikát felület nélkül?

Megoldás megnyitása

Önálló osztályba teszed (nincs vezérlő a kódban), az állapotot IProgress<T> vagy események jelzik; a teszt ideiglenes mappafával futtatja, és az Eredmeny értékeit ellenőrzi.

Gyors önellenőrzés

Válaszolj fejben, és csak utána nyisd le a választ.

1. Miért nem ír a háttérszál vezérlőt?

A vezérlők csak a felületi szálról érhetők el; a háttérszál az adatot közzéteszi, a felület időzítővel olvassa.

2. Mire jó az Interlocked?

Közös számlálók szálbiztos módosítására.

3. Hogyan működik a szünet?

ManualResetEventSlim: a munka a Wait(token) hívásban vár, amíg a Set() el nem engedi.

4. Hogyan különböztetjük meg az időtúllépést a Mégse-től?

Az időzítős forrás IsCancellationRequested tulajdonságából.

5. Mit teszel üres mappánál?

A Maximum legalább 1; érthető üzenet; nincs kivétel.

6. Miért új forrás minden indításhoz?

A megszakított forrás végleg megszakított marad.

7. Mit teszel bezáráskor?

Megszakítod a munkát, e.Cancel = true mellett megvárod, majd bezársz.

A 11. fejezet összefoglalása: mit tudsz már?

  • Időzítőt használsz, és az időt Stopwatch-csal méred.
  • Folyamatot jelzel (határozott és határozatlan), becsült idővel.
  • Érted a felületi szálat, a blokkolást és a holtpontot.
  • async/await, Task.Run, Parallel segítségével háttérben dolgozol.
  • Biztonságosan frissíted a felületet, és védetten kezeled a közös adatokat.
  • Megszakítható, szüneteltethető, időkorlátos műveleteket írsz.

Következő fejezet: OOP és programtervezés

A 12. fejezetben rendezett, újrahasználható szerkezetet adsz a programoknak: osztályok, felelősségek, rétegek.