Grafikus C# – Windows Forms tanfolyam › 11.6 lecke Windows Forms · 11.6 lecke · háttérmunka A Háttérben végzett műveletek
Task.Run és az await (11.5 lecke) az alap. Ebben a leckében a háttérmunka gyakorlati oldalát nézzük: a Windows Forms régi, de sok programban még ma is használt eszközét, a BackgroundWorkert (érdemes ismerned, mert régi kódban találkozol vele); a Task.Run haladóbb használatát (hosszú munka, haladás-jelzés, eredmény); a párhuzamos feldolgozást (Parallel.For), amely a több processzormag erejét használja, és a vele járó versenyhelyzetet; végül azt, hogyan választod ki a feladathoz illő eszközt. A gyakorlóprojekt egy képfeldolgozó: nagy kép szűrése a felület lefagyása nélkül.
A lecke végére
- használod a
BackgroundWorkert (DoWork,ProgressChanged,RunWorkerCompleted, megszakítás), és tudod, mikor van értelme; - a
Task.Runsegítségével eredményt adó, haladást jelentő háttérmunkát írsz; - párhuzamos ciklust (
Parallel.For) használsz, és felismered a versenyhelyzetet (Interlocked,lock); - tudod, hogy a
Bitmapnem szálbiztos, ésLockBitssegítségével dolgozol; - feladathoz választasz eszközt (
Task.Run,...Async,Parallel,Thread,BackgroundWorker); - a nagy fájl keresését (10.2 lecke) átírod aszinkronra.
Mielőtt elkezded
Ehhez a leckéhez a 11.3–11.5 lecke (felületi szál, blokkolás, async/await) és a 9.1–9.2 lecke (Bitmap) szükséges.
BackgroundWorker: az esemény-alapú háttérmunka
A BackgroundWorker a Toolboxból a Formra tehető komponens, három eseménnyel: a DoWork háttérszálon fut; a ProgressChanged és a RunWorkerCompleted a felületi szálon (ezért ott a vezérlők közvetlenül elérhetők, Invoke nélkül). Az async/await előtti időkben ez volt a szokásos megoldás, ma is sok meglévő programban találkozol vele:
// beállítás (Designer vagy Load): haladás és megszakítás engedélyezése
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
private void btnIndit_Click(object sender, EventArgs e)
{
btnIndit.Enabled = false;
btnMegse.Enabled = true;
pb.Value = 0;
bw.RunWorkerAsync((int)nudDb.Value); // a paraméter a DoWork e.Argument értéke (a bemenetet itt, a felületi szálon olvassuk ki)
}
// 1. DoWork: HÁTTÉRSZÁLON! Vezérlőhöz nem nyúlhat.
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
int db = (int)e.Argument;
long osszeg = 0;
for (int i = 1; i <= db; i++)
{
if (bw.CancellationPending) // a megszakítás kérésének ellenőrzése
{
e.Cancel = true;
return;
}
osszeg += NehezSzamitas(i);
if (i % 100 == 0) { bw.ReportProgress(i * 100 / db); } // haladás jelzése: a felületi szálra érkezik
}
e.Result = osszeg; // az eredmény a RunWorkerCompleted-ben
}
// 2. ProgressChanged: a FELÜLETI szálon
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pb.Value = e.ProgressPercentage;
}
// 3. RunWorkerCompleted: a FELÜLETI szálon: kész, hibás vagy megszakított
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
btnIndit.Enabled = true;
btnMegse.Enabled = false;
if (e.Error != null) { lblAllapot.Text = "Hiba: " + e.Error.Message; } // a DoWork kivétele ide kerül
else if (e.Cancelled) { lblAllapot.Text = "Megszakítva."; }
else { lblAllapot.Text = "Eredmény: " + (long)e.Result; }
}
private void btnMegse_Click(object sender, EventArgs e)
{
bw.CancelAsync(); // csak kéri a megszakítást: a DoWork ciklusnak kell figyelnie (CancellationPending)
}DoWork a munka (háttérszál), a ProgressChanged a haladás, a RunWorkerCompleted a befejezés (felületi szál). Ez a felosztás kényelmes: nem kell Invoke, de szétszabdalja a logikát három metódusra.e.Argument, az eredmény az e.Result. A bemenetet a felületi szálon olvasd ki, és a RunWorkerAsync paramétereként add át; a DoWork-ban vezérlőt ne érj.CancelAsync() csak kéri a leállítást (a CancellationPending igaz lesz): a DoWork ciklusának kell ellenőriznie és kilépnie, e.Cancel = true beállítással.DoWork kezeletlen kivétele nem omlasztja össze a programot: a RunWorkerCompleted e.Error tulajdonságába kerül. Ha nem ellenőrzöd az e.Error értékét, és az e.Result-ot olvasod, TargetInvocationException jön.Jósolj!
A DoWork kezelő közvetlenül írja a lblAllapot.Text értékét. Mi történik?
Jóslat ellenőrzése
InvalidOperationException (cross-thread), amely a RunWorkerCompleted e.Error értékeként jelenik meg (a program nem omlik össze, de a munka félbeszakad). A haladáshoz a ReportProgress való, az eredményhez az e.Result.
Task.Run haladóbban: ugyanaz egyszerűbben
Ugyanezt a feladatot a Task.Run és az await egyetlen metódusban oldja meg. A haladás jelzésére a Progress<T> való: a felületi szálon létrehozva a visszahívás is oda érkezik (részletesen a 11.7 leckében), a megszakítás a CancellationToken (11.8 lecke):
private async void btnIndit_Click(object sender, EventArgs e)
{
btnIndit.Enabled = false;
pb.Value = 0;
int db = (int)nudDb.Value; // a bemenet a felületi szálon
Progress<int> halad = new Progress<int>(szazalek => pb.Value = szazalek); // a visszahívás a FELÜLETI szálon fut
try
{
long osszeg = await Task.Run(() => Szamol(db, halad));
lblAllapot.Text = "Eredmény: " + osszeg;
}
catch (Exception ex)
{
lblAllapot.Text = "Hiba: " + ex.Message;
}
finally
{
btnIndit.Enabled = true;
}
}
// a háttérszálon futó munka: vezérlő nélkül, a haladást a Progress-en át jelzi
private long Szamol(int db, IProgress<int> halad)
{
long osszeg = 0;
for (int i = 1; i <= db; i++)
{
osszeg += NehezSzamitas(i);
if (i % 100 == 0) { halad.Report(i * 100 / db); }
}
return osszeg;
}| BackgroundWorker | Task.Run + await | |
|---|---|---|
| Szerkezet | Három esemény, szétszórt kód. | Egy metódus, soronként olvasható. |
| Eredmény, kivétel | e.Result, e.Error az eseményben. | Az await értéke, try–catch. |
| Haladás | ReportProgress (csak százalék + egy objektum). | IProgress<T> tetszőleges típussal. |
| Megszakítás | CancelAsync, CancellationPending. | CancellationToken (a szabvány). |
| Több művelet együtt | Nehézkes. | WhenAll, WhenAny. |
| Mikor? | Meglévő kód karbantartása. | Új kód: ezt válaszd. |
Jósolj!
A Progress<int> példány a háttérszálon jön létre (a Task.Run lambdájában: new Progress<int>(p => pb.Value = p)). Mi történik a Report hívásakor?
Jóslat ellenőrzése
A visszahívás nem a felületi szálon fut, mert a Progress<T> a létrehozásának szálához tartozó környezetet jegyzi meg: háttérszálon létrehozva a pb.Value írása cross-thread kivételt (vagy hibát) okoz. A Progress<T>-t mindig a felületi szálon hozd létre.
Párhuzamos feldolgozás és a versenyhelyzet
A Task.Run a felületet védi, de a számítás egy magon fut. Ha a munka független elemekből áll (egy kép sorai, egy lista elemei), a Parallel.For a ciklust több szálon, egyszerre futtatja, így a több processzormag is dolgozik:
// SOROS: egy szál, egy mag
for (int y = 0; y < magassag; y++) { SortFeldolgoz(y); }
// PÁRHUZAMOS: a sorokat több szál osztja meg (a sorrend nem garantált!)
Parallel.For(0, magassag, y => SortFeldolgoz(y));
// FONTOS: a Parallel.For BLOKKOLJA a hívó szálat, amíg a ciklus végez!
// A felületi szálon közvetlenül hívva fagyaszt: mindig Task.Run-ba tesszük.
await Task.Run(() => Parallel.For(0, magassag, y => SortFeldolgoz(y)));
// VERSENYHELYZET: a közös változót több szál egyszerre írja
long db = 0;
Parallel.For(0, 1000000, i => { db++; }); // HIBÁS: az eredmény nem 1 000 000 (elveszett növelések)
long jo = 0;
Parallel.For(0, 1000000, i => { Interlocked.Increment(ref jo); }); // HELYES: oszthatatlan növelés
// zárral (összetettebb közös adatnál)
object zar = new object();
List<int> talalatok = new List<int>();
Parallel.For(0, 1000, i =>
{
if (i % 7 == 0)
{
lock (zar) { talalatok.Add(i); } // a List<T> NEM szálbiztos: a közös listához zár kell
}
});Parallel.For akkor gyorsít, ha a munka számításigényes és az elemek függetlenek. Apró művelet (egy összeadás) esetén a szálkezelés költsége nagyobb, mint a nyereség. Mérd meg (Stopwatch).db++ három lépés (olvas, növel, ír): két szál összefésülődő lépései elveszítik a növelések egy részét. Az eredmény véletlenszerű: egyszer 998 342, máskor 999 001. A hiba nehezen reprodukálható, és „általában működik”.Interlocked.Increment oszthatatlan (atomi) művelet egyszerű számlálóhoz. Összetettebb közös adatnál (lista, szótár) a lock kritikus szakaszt ad: egyszerre egy szál léphet be. Kevesebb közös adat, kevesebb zár: a legjobb, ha a szálak külön adaton dolgoznak, és az eredményt a végén összegzed.Bitmap GetPixel/SetPixel hívásai több szálról egyszerre InvalidOperationException (Object is currently in use elsewhere) vagy hibás képet okozhatnak. Párhuzamos képfeldolgozáshoz a képpontokat LockBits segítségével bájttömbbe másold, a tömbön dolgozz (a sorok függetlenek), majd írd vissza.Jósolj!
A Parallel.For(0, 1000000, i => { db++; }) ciklus után a db értéke 1 000 000 lesz?
Jóslat ellenőrzése
Nem (többmagos gépen): jóval kevesebb, és futásonként más, mert a szálak felülírják egymás növeléseit (versenyhelyzet). Egymagos környezetben „véletlenül” jó lehet: éppen ez veszélyes. Interlocked.Increment kell.
Melyik eszköz mikor?
| Feladat | Eszköz |
|---|---|
| Fájl, hálózat, adatbázis (várakozás) | Beépített ...Async metódus + await. Nincs szál. |
| Hosszú számítás (egy darab) | await Task.Run(...) |
| Sok független elem számítása | await Task.Run(() => Parallel.For(...)) vagy PLINQ (AsParallel()) |
| Egyszeri vagy ismétlődő késleltetés, kijelzés | Timer, await Task.Delay |
| Hosszú ideig (a program teljes életén át) futó figyelő | Task.Factory.StartNew(..., TaskCreationOptions.LongRunning) vagy külön Thread (IsBackground = true) |
Meglévő, BackgroundWorker-t használó kód | Maradhat; új funkciót már Task-kal írj. |
// A 10.2 lecke keresése DoEvents-szel volt; most aszinkron: a felület közben él, a haladás a Progress-en át érkezik
private async void btnKeres_Click(object sender, EventArgs e)
{
string szo = txtSzo.Text.Trim(); // a bemenet a felületi szálon
string ut = txtUt.Text;
if (szo.Length == 0 || !File.Exists(ut)) { return; }
btnKeres.Enabled = false;
lstTalalat.Items.Clear();
Progress<int> halad = new Progress<int>(p => pbHaladas.Value = p);
try
{
List<string> talalatok = await Task.Run(() => KeresFajlban(ut, szo, halad)); // az eredmény: a háttérben gyűjtött lista
lstTalalat.Items.AddRange(talalatok.ToArray()); // egyszerre, a felületi szálon (nem soronként)
tsslUzenet.Text = talalatok.Count + " találat.";
}
catch (IOException ex) { tsslUzenet.Text = ex.Message; }
finally { btnKeres.Enabled = true; }
}
private List<string> KeresFajlban(string ut, string szo, IProgress<int> halad)
{
List<string> talalatok = new List<string>();
long sorszam = 0;
using (FileStream fs = new FileStream(ut, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (StreamReader olvaso = new StreamReader(fs))
{
string sor;
while ((sor = olvaso.ReadLine()) != null)
{
sorszam++;
if (sor.IndexOf(szo, StringComparison.CurrentCultureIgnoreCase) >= 0)
{
talalatok.Add(sorszam + ": " + sor);
if (talalatok.Count >= 1000) { break; }
}
if (sorszam % 5000 == 0) { halad.Report((int)(fs.Position * 100 / Math.Max(1, fs.Length))); }
}
}
return talalatok;
}Jósolj!
A fenti keresés közben a háttérszál a talalatok listához ad elemeket, miközben a felületi szál (a Progress visszahívásban) ugyanezt a listát olvassa. Mi a veszély?
Jóslat ellenőrzése
A List<T> nem szálbiztos: egyidejű írás és olvasás InvalidOperationException kivételt (a gyűjtemény módosult) vagy hibás adatot okozhat. A lecke megoldása a lista átadása a végén (a háttérszál végez vele, a felületi szál az await után használja): egyszerre egyetlen szál éri el.
Mit tegyél ide: a képfeldolgozó: az eredeti és a szűrt kép, a négy módszer időmérése, a folyamatjelző.Javasolt fájlnév: winforms-11-6-kepfeldolgozo.webp · Alt szöveg: A képfeldolgozó program
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. A felület lefagy a Parallel.For alatt
private void btn_Click(object sender, EventArgs e)
{
Parallel.For(0, 2000, y => Sor(y)); // a hívó (felületi) szálat blokkolja
}Hiba és javítás
A Parallel.For megvárja a ciklus végét. Tedd await Task.Run(() => Parallel.For(...)) alakba.
2. A számláló értéke nem stimmel
Parallel.ForEach(elemek, x => { if (Jo(x)) { jok++; } });Hiba és javítás
Versenyhelyzet. Interlocked.Increment(ref jok), vagy szálanként helyi számláló, a végén összegezve.
3. InvalidOperationException: Object is currently in use elsewhere
Parallel.For(0, h, y => { for (int x = 0; x < w; x++) { kep.SetPixel(x, y, szin); } });Hiba és javítás
A Bitmap nem szálbiztos. Másold a képpontokat LockBits segítségével bájttömbbe, és a tömbön dolgozz.
4. A haladás-jelzés cross-thread kivételt ad
await Task.Run(() =>
{
Progress<int> h = new Progress<int>(p => pb.Value = p); // háttérszálon létrehozva
h.Report(50);
});Hiba és javítás
A Progress<T>-t a felületi szálon kell létrehozni (a Task.Run előtt), és átadni a háttérmunkának.
5. TargetInvocationException a RunWorkerCompleted-ben
lblAllapot.Text = e.Result.ToString(); // nincs e.Error ellenőrzésHiba és javítás
Ha a DoWork kivételt dobott, az e.Result olvasása újra dobja. Előbb ellenőrizd az e.Error és e.Cancelled tulajdonságot.
6. A megszakítás nem hat
A Mégse gomb után a feldolgozás tovább fut. A Ok és javítás
CancelAsync() csak kéri a leállítást: a DoWork ciklusnak kell figyelnie a CancellationPending jelzőt (vagy CancellationToken esetén a token állapotát), és kilépnie.
Mini projekt: képfeldolgozó
Készíts programot, amely egy nagy képet szűr (szürkeárnyalat, majd elmosás), négyféleképpen futtatva, és mutatja a különbségeket:
- Felület:
pbForraséspbEredmeny(PictureBox,Zoom); Kép generálása (2400 × 1600 pixeles színátmenetes, zajosBitmap, a program maga állítja elő), Megnyitás... (a 9.1 leckeKepBetoltmetódusával);cmbMod(1. Felületi szálon, 2. BackgroundWorker, 3. Task.Run, 4. Task.Run + Parallel.For);cmbSzuro(Szürkeárnyalat, Negatív, Elmosás (box blur 5×5)); Indítás, Mégse;pb(folyamatjelző),lblIdo;lstNaplo; az animáció és késésmérő (11.3 lecke). - Szálbiztos feldolgozás: a forráskép képpontjait
LockBitssegítségévelbyte[]tömbbe másolod (Format32bppArgb), a szűrő a tömbön dolgozik (a sorok függetlenek), az eredményt újBitmapba írod vissza; aBitmapot nem éri több szál egyszerre. - 1. mód: a felületi szálon; az animáció áll, a késés nagy.
- 2. mód:
BackgroundWorker:DoWork(a feldolgozás, haladás soronként 5%-onként),ProgressChanged,RunWorkerCompleted(e.Error,e.Cancelledellenőrzése). - 3. mód:
await Task.RunésProgress<int>; a bemenet kiolvasása a felületi szálon. - 4. mód:
await Task.Run(() => Parallel.For(0, magassag, sor)); a haladás:Interlocked.Incrementszámláló (a kész sorok), a jelentés nem minden sorra (legfeljebb 20 ms-onként). - Megszakítás: a Mégse a
BackgroundWorkernélCancelAsync; aTask-oknál egyvolatile bool megszakitjelző (a 11.8 leckeCancellationTokenmegoldását előkészítve); a feldolgozó ciklusok ellenőrzik. - Összehasonlítás: a naplóban a négy mód ideje és a legnagyobb késés; a 4. mód a többmagos gépen gyorsabb; a kép azonos eredményt ad (az első 1000 képpont összehasonlítása).
- Hibatűrés: nincs kép (érthető üzenet); a régi eredménykép felszabadítása (9.8 lecke); a Form bezárása futó feldolgozás közben; a gombok állapota.
- Teszteld: mind a négy mód mindhárom szűrővel, a mozgó négyzet (a késésmérő), megszakítás, dupla indítás, egy nagyon kicsi kép (1 × 1), a Feladatkezelőben a processzorhasználat a 3. és a 4. módnál.
Megoldás megnyitása (próbáld ki előbb önállóan!)
A központi rész a szálbiztos Szur metódus (LockBits + bájttömb) és a négy futtatási mód.
private Bitmap forras = null;
private volatile bool megszakit = false;
private int futo = 0;
// ---- kép generálása ----
private void btnGeneral_Click(object sender, EventArgs e)
{
Bitmap uj = new Bitmap(2400, 1600, PixelFormat.Format32bppArgb);
Random v = new Random(1);
for (int y = 0; y < uj.Height; y += 4)
{
for (int x = 0; x < uj.Width; x += 4)
{
Color c = Color.FromArgb(x * 255 / uj.Width, y * 255 / uj.Height, v.Next(256));
for (int dy = 0; dy < 4; dy++) { for (int dx = 0; dx < 4; dx++) { uj.SetPixel(x + dx, y + dy, c); } }
}
}
Bitmap regi = forras;
forras = uj;
pbForras.Image = forras;
if (regi != null) { regi.Dispose(); }
}
// ---- szálbiztos szűrő: a képpontok bájttömbben ----
private Bitmap Szur(Bitmap kep, int szuro, bool parhuzamos, Action<int> sorKesz)
{
int w = kep.Width, h = kep.Height;
BitmapData bd = kep.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
int stride = bd.Stride;
byte[] be = new byte[stride * h];
Marshal.Copy(bd.Scan0, be, 0, be.Length);
kep.UnlockBits(bd);
byte[] ki = new byte[be.Length];
Action<int> sor = y =>
{
if (megszakit) { return; }
for (int x = 0; x < w; x++)
{
int j = y * stride + x * 4;
if (szuro == 0) // szürkeárnyalat
{
byte g = (byte)(0.114 * be[j] + 0.587 * be[j + 1] + 0.299 * be[j + 2]);
ki[j] = ki[j + 1] = ki[j + 2] = g; ki[j + 3] = 255;
}
else if (szuro == 1) // negatív
{
ki[j] = (byte)(255 - be[j]); ki[j + 1] = (byte)(255 - be[j + 1]); ki[j + 2] = (byte)(255 - be[j + 2]); ki[j + 3] = 255;
}
else // elmosás 5×5 (a bemenet olvasása, a kimenet írása: külön tömb, nincs verseny)
{
int r = 0, g = 0, b = 0, n = 0;
for (int dy = -2; dy <= 2; dy++)
{
int yy = y + dy;
if (yy < 0 || yy >= h) { continue; }
for (int dx = -2; dx <= 2; dx++)
{
int xx = x + dx;
if (xx < 0 || xx >= w) { continue; }
int k = yy * stride + xx * 4;
b += be[k]; g += be[k + 1]; r += be[k + 2]; n++;
}
}
ki[j] = (byte)(b / n); ki[j + 1] = (byte)(g / n); ki[j + 2] = (byte)(r / n); ki[j + 3] = 255;
}
}
if (sorKesz != null) { sorKesz(y); }
};
if (parhuzamos) { Parallel.For(0, h, sor); }
else { for (int y = 0; y < h; y++) { sor(y); } }
if (megszakit) { return null; }
Bitmap eredmeny = new Bitmap(w, h, PixelFormat.Format32bppArgb);
BitmapData bd2 = eredmeny.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(ki, 0, bd2.Scan0, ki.Length);
eredmeny.UnlockBits(bd2);
return eredmeny;
}
private void Mutat(Bitmap uj, string mod, long ms)
{
if (uj == null) { Naploz(mod + ": megszakítva"); return; }
Bitmap regi = pbEredmeny.Image as Bitmap;
pbEredmeny.Image = uj;
if (regi != null) { regi.Dispose(); }
Naploz(mod + ": " + ms + " ms, legnagyobb késés: " + legnagyobbKeses + " ms");
}
private void Naploz(string s) { lstNaplo.Items.Insert(0, DateTime.Now.ToString("HH:mm:ss") + " " + s); }
private void Fut(bool fut) { btnIndit.Enabled = !fut; btnMegse.Enabled = fut; cmbMod.Enabled = !fut; cmbSzuro.Enabled = !fut; }
// ---- futtatás ----
private async void btnIndit_Click(object sender, EventArgs e)
{
if (forras == null) { tsslUzenet.Text = "Előbb generálj vagy nyiss meg egy képet."; return; }
int mod = cmbMod.SelectedIndex, szuro = cmbSzuro.SelectedIndex;
int magassag = forras.Height;
megszakit = false;
legnagyobbKeses = 0;
pb.Value = 0;
Fut(true);
Stopwatch sw = Stopwatch.StartNew();
try
{
if (mod == 0) // 1. felületi szálon
{
Mutat(Szur(forras, szuro, false, null), "1. Felületi szál", sw.ElapsedMilliseconds);
}
else if (mod == 1) // 2. BackgroundWorker
{
bw.RunWorkerAsync(new int[] { szuro });
return; // a Fut(false) a RunWorkerCompleted-ben
}
else
{
bool par = mod == 3;
int kesz = 0;
Progress<int> halad = new Progress<int>(p => pb.Value = Math.Min(100, p)); // a FELÜLETI szálon létrehozva
Stopwatch jelent = Stopwatch.StartNew();
Bitmap forrasMasolat = forras; // a háttérben csak olvassuk (LockBits), a felület közben nem módosítja
Bitmap uj = await Task.Run(() => Szur(forrasMasolat, szuro, par, y =>
{
int n = Interlocked.Increment(ref kesz);
if (n % 40 == 0 && jelent.ElapsedMilliseconds > 20) { jelent.Restart(); ((IProgress<int>)halad).Report(n * 100 / magassag); }
}));
if (IsDisposed) { if (uj != null) { uj.Dispose(); } return; }
Mutat(uj, mod == 2 ? "3. Task.Run" : "4. Task.Run + Parallel.For", sw.ElapsedMilliseconds);
}
}
catch (Exception ex) { tsslUzenet.Text = "Hiba: " + ex.Message; }
finally
{
if (mod != 1 && !IsDisposed) { pb.Value = 100; Fut(false); }
}
}
// ---- BackgroundWorker ----
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
int szuro = ((int[])e.Argument)[0];
int kesz = 0, magassag = forras.Height;
Stopwatch sw = Stopwatch.StartNew();
Bitmap uj = Szur(forras, szuro, false, y =>
{
kesz++;
if (kesz % (magassag / 20) == 0) { bw.ReportProgress(kesz * 100 / magassag); }
});
if (uj == null) { e.Cancel = true; return; }
e.Result = new object[] { uj, sw.ElapsedMilliseconds };
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e) { pb.Value = Math.Min(100, e.ProgressPercentage); }
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Fut(false);
if (e.Error != null) { tsslUzenet.Text = "Hiba: " + e.Error.Message; return; }
if (e.Cancelled) { Naploz("2. BackgroundWorker: megszakítva"); return; }
object[] r = (object[])e.Result;
pb.Value = 100;
Mutat((Bitmap)r[0], "2. BackgroundWorker", (long)r[1]);
}
private void btnMegse_Click(object sender, EventArgs e)
{
megszakit = true;
if (bw.IsBusy) { bw.CancelAsync(); }
}Mit tegyél ide: a kész képfeldolgozó a négy mód időmérésével.Javasolt fájlnév: winforms-11-6-kesz.webp · Alt szöveg: A kész képfeldolgozó
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. Fájlok kötegelt kicsinyítése
Egy mappa 200 képét kicsinyítsd (9.2 lecke) a felület lefagyása nélkül, haladás-jelzéssel. Mely részek mennek a háttérbe? A betöltés, a kicsinyítés és a mentés a Egy lehetséges megoldás
Task.Run lambdájában (vezérlő nélkül, a fájlok listáját előre átadva); a haladás Progress<int>; a képenkénti using blokk felszabadít. A fájlonkénti párhuzamosítás (Parallel.ForEach) a gyors lemezen gyorsíthat.
2. PLINQ
Egy 1 milliós szám-listából a prímeket kell kiszűrni. Hogyan használod a PLINQ-t?Egy lehetséges megoldás
await Task.Run(() => szamok.AsParallel().Where(Prim).ToList()). A AsParallel() a kifejezést több szálon végzi; a sorrend nem garantált (ha kell: AsOrdered()).
3. Háttér-figyelő
Egy háttérszál percenként ellenőriz egy mappát, és az új fájlokról a felületet értesíti. Mit használsz? Egy Egy lehetséges megoldás
Timer (felületi szál, egyszerű ellenőrzés await-tel), vagy a FileSystemWatcher (az eseményei másik szálon érkeznek: BeginInvoke kell a felületi frissítéshez). Külön Thread csak hosszú, folyamatos ciklushoz indokolt.
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. Melyik szálon?
Melyik szálon fut a BackgroundWorker három eseménye?Megoldás megnyitása
DoWork: háttérszál; ProgressChanged és RunWorkerCompleted: a felületi szál.
2. Eszköz
Melyiket választod: (a) fájl letöltése; (b) 50 millió elem összegzése; (c) minden másodpercben frissülő óra? (a) Megoldás megnyitása
HttpClient + await; (b) await Task.Run (esetleg Parallel); (c) Timer.
Közepes szint
3. Interlocked
Mi a különbség a A db++ és az Interlocked.Increment(ref db) között?Megoldás megnyitása
db++ három lépés (olvas, növel, ír), amelyek két szálon összefésülődhetnek; az Interlocked.Increment oszthatatlan (atomi), nincs elveszett növelés.
4. Lista és szálak
Mit használhatsz a List<T> helyett, ha több szál ad hozzá elemeket?Megoldás megnyitása
ConcurrentBag<T> vagy ConcurrentQueue<T> (System.Collections.Concurrent), vagy a lista lock-kal védve; legjobb a szálanként külön lista, a végén egyesítve.
Kihívás szint
5. Hány szál?
A Alapból a szálkészlet méretezése és a processzormagok száma szerint. Korlát: Parallel.For hány szálat használ, és hogyan korlátozod?Megoldás megnyitása
new ParallelOptions { MaxDegreeOfParallelism = 2 } (például, hogy a felület és a többi program is kapjon processzoridőt).ParallelOptions opciok = new ParallelOptions { MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount - 1) };Parallel.For(0, h, opciok, y => Sor(y));
6. Gyorsulás mérése
Négymagos gépen a párhuzamos változat 2,6-szor gyorsabb, nem 4-szer. Miért? A szálkezelés költsége, a közös erőforrások (memória-sávszélesség), a felület és a többi program is használ processzoridőt, és a munka egy része sorosan marad (Amdahl törvénye). A tökéletes gyorsulás ritka.Megoldás megnyitása
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. A Egyetlen metódusban olvasható, A felületi szálon, a A hívó szálat: felületi szálon Több szál egyszerre írja ugyanazt az adatot, és elvész egy módosítás; az eredmény véletlenszerű. A GDI+ objektum egyidejű használata hibát okoz; a képpontokat Nem: a beépített 1. Melyik BackgroundWorker esemény fut háttérszálon?
DoWork; a másik kettő a felületi szálon.2. Miért jobb a Task.Run + await a BackgroundWorkernél?
try–catch, eredmény, CancellationToken, WhenAll.3. Hol hozod létre a Progress<T>-t?
Task.Run előtt; így a visszahívás is a felületi szálon fut.4. Mit blokkol a Parallel.For?
await Task.Run alatt használd.5. Mi a versenyhelyzet?
6. Miért nem szálbiztos a Bitmap?
LockBits-tel bájttömbbe kell másolni.7. Kell-e szál az I/O-hoz?
...Async metódusok szál nélkül várnak.
Mit tudsz már?
- Használod és megérted a
BackgroundWorkert (régi kód), de új kódotTask-kal írsz. Task.Runsegítségével eredményt adó, haladást jelentő háttérmunkát készítesz.- Párhuzamos ciklust használsz, és kezeled a versenyhelyzetet.
- Tudod, hogy a
Bitmapés aList<T>nem szálbiztos. - Feladathoz választasz eszközt, és a nagy fájl keresését aszinkronra írod át.
Következő lecke: UI-vezérlőelemek biztonságos frissítése
Invoke, BeginInvoke, Progress<T>, a felületi frissítések szabályozása (throttling), és a Form bezárása futó háttérmunka mellett.



