Grafikus C# – Windows Forms tanfolyam › 11.5 lecke Windows Forms · 11.5 lecke · aszinkron programozás Eddig azt tanultad, mit ne csinálj a felületi szálon. Most jön a megoldás: az Task, async és await
async/await, a modern C# egyik legfontosabb eszköze. Segítségével úgy írhatsz hosszú műveletet, hogy a kód olvasható, soronként követhető marad, a felület közben él, és az eredmény után a program ugyanott folytatódik, ahol a felületi szálon biztonságosan frissítheted a vezérlőket. Az alap a Task: egy „ígéret” egy munkára, amely majd elkészül (eredménnyel vagy hibával). Ebben a leckében megtanulod a Task és Task<T> típusokat, az async/await szabályait, a Task.Run szerepét a számításigényes munkánál, a valódi aszinkron I/O-t, a kivételek viselkedését és több feladat együttes kezelését (WhenAll).
A lecke végére
- érted, mi a
Taskés aTask<T>, és mit jelent a kész, hibás, megszakított állapot; - helyesen írsz
asyncmetódust (Task,Task<T>;async voidcsak eseménykezelőben), ésawait-tel várod; - tudod: az
asyncönmagában nem indít szálat; azawaitfelszabadítja a felületi szálat; - a
Task.Runsegítségével számításigényes munkát tolsz háttérszálra, az I/O-hoz pedig a beépített...Asyncmetódusokat használod; - kezeled a kivételeket (
try–catchazawaitkörül), és tudod, miért veszélyes az elveszett (fire-and-forget) feladat; - több feladatot egyszerre indítasz és várod (
Task.WhenAll,Task.WhenAny).
Mielőtt elkezded
Ehhez a leckéhez a 11.3–11.4 lecke (felületi szál, blokkolás, holtpont), a 4.7 lecke (kivételkezelés) és az 5.1 lecke (események) szükséges. Újabb .NET (5 és fölötti) vagy .NET Framework 4.5+ szükséges: ezekben az async/await és a Task elérhető.
A Task: ígéret egy munkára
A Task egy folyamatban lévő vagy kész munkát képviselő objektum, amely nem ad eredményt (mint a void metódus). A Task<T> T típusú eredményt is ad. A Task három végállapota: kész (RanToCompletion), hibás (Faulted: kivétel történt), megszakított (Canceled, 11.8 lecke):
// munka indítása a szálkészleten (háttérszálon); azonnal visszatér egy Task-kal
Task<long> feladat = Task.Run(() => PrimekSzama(5000000));
// ... a hívó közben mást is csinálhat; az eredmény később:
bool kesz = feladat.IsCompleted; // kész, hibás vagy megszakított?
bool hibas = feladat.IsFaulted;
bool megszakitott = feladat.IsCanceled;
// előre kész Task: ha az eredmény már megvan (például gyorsítótárból)
Task<int> megvan = Task.FromResult(42);
Task semmi = Task.CompletedTask;
// késleltetés (Thread.Sleep nem blokkoló párja)
Task varakozas = Task.Delay(2000);Task nem szál. Egy Task lehet szálon futó számítás (Task.Run), de lehet egy várakozás (Task.Delay), egy hálózati kérés vagy egy lemezolvasás is, amelyhez egyáltalán nincs szál: az operációs rendszer jelzi, ha kész.Task<T> eredményét await adja át (a .Result blokkol: 11.4 lecke). Az eredmény csak a RanToCompletion állapotban érvényes.IsCompleted igaz minden végállapotban (kész, hibás, megszakított). Az await a hibás Task kivételét újra eldobja (lásd lent).Thread egy végrehajtási szál (drága, kézzel kezelt); a Task magasabb szintű: a szálkészlet (thread pool) a munkát szálakra osztja, és újrahasznosítja a szálakat. Új kódban Task.Jósolj!
A program a var t = Task.Delay(3000); sort hajtja végre, és az await nélkül tovább halad. Mikor telik le a három másodperc?
Jóslat ellenőrzése
A Task azonnal elindul, és a három másodperc a háttérben telik, függetlenül attól, hogy vársz-e rá. A következő sor azonnal lefut (nem várakozik). A Task egy futó „ígéret”: az await teszi a hívó kódot a végéig várakozóvá.
async és await: szabályok
// az eseménykezelő async void: itt (és csak itt) engedélyezett
private async void btnSzamol_Click(object sender, EventArgs e)
{
btnSzamol.Enabled = false;
lblAllapot.Text = "Számolok..."; // ez LÁTSZIK: a felületi szál az await-nél felszabadul
try
{
long db = await Task.Run(() => PrimekSzama(5000000)); // a számítás háttérszálon; a felület közben él
lblAllapot.Text = "Prímek: " + db; // az await után ismét a FELÜLETI szálon vagyunk: a vezérlő elérhető
}
finally
{
btnSzamol.Enabled = true;
}
}
// saját async metódus: Task / Task<T> a visszatérési típus; az Async utótag a megszokás
private async Task<string> BetoltAsync(string ut)
{
string szoveg = await File.ReadAllTextAsync(ut); // nincs szál: az operációs rendszer jelzi, ha kész
return szoveg.ToUpper(); // az eredmény Task<string>-be csomagolva
}
// hívása másik async metódusból
string s = await BetoltAsync(ut);Az await pontja
Az await kifejezésnél a metódus megáll, és visszaadja a vezérlést a hívónak (a felületi szál tovább dolgozza az üzeneteket). Amikor a várt Task elkészül, a metódus onnan folytatódik, ahol megállt, a felületi szálon. Ezért az await utáni sorban a vezérlők elérhetők.
Mit nem csinál az async?
Az async kulcsszó önmagában nem indít szálat, és nem tesz semmit gyorssá. Csak lehetővé teszi az await használatát. Ha egy async metódusban hosszú, szinkron számítás van await nélkül, az a hívó szálon fut, és blokkol.
| Visszatérési típus | Mikor? |
|---|---|
async Task | Nincs eredmény (mint a void), de a hívó await -elheti, és a kivétel eljut hozzá. |
async Task<T> | Van T típusú eredmény. |
async void | Csak eseménykezelő. Nem várható, a kivétele nem fogható el a hívónál (lásd lent). |
Jósolj!
Az eseménykezelő await Task.Run(() => Szamol()) sora előtt a felület szálán fut a kód. Melyik szálon fut a lblAllapot.Text = ... sor az await után?
Jóslat ellenőrzése
A felületi szálon: az await megjegyzi a környezetet (SynchronizationContext), és a folytatást oda üzeni vissza. Ezért kényelmes a Windows Formsban: az await utáni sorban nem kell Invoke, a vezérlő közvetlenül írható.
Task.Run és a valódi aszinkron I/O
| Munka fajtája | Példa | Megoldás |
|---|---|---|
| CPU-igényes (számítás) | prímkeresés, képfeldolgozás, rendezés, tömörítés | await Task.Run(() => ...): a szálkészlet egy szálán fut. |
| I/O-igényes (várakozás) | fájl, hálózat, adatbázis, külső program | Beépített ...Async metódus: ReadAllTextAsync, HttpClient.GetStringAsync, StreamReader.ReadLineAsync. Nincs szál, nem kell Task.Run. |
// CPU-igényes: Task.Run
private async void btnKep_Click(object sender, EventArgs e)
{
Bitmap forras = (Bitmap)pbKep.Image;
Bitmap eredmeny = await Task.Run(() => Szurkearnyalat(forras)); // a feldolgozás a háttérben (a Bitmap-et ne érje közben a felület!)
pbKep.Image = eredmeny; // az await után: a felületi szálon
}
// I/O-igényes: beépített aszinkron metódus (nincs Task.Run!)
private static readonly HttpClient kliens = new HttpClient(); // egyetlen példány az egész programban
private async void btnLetolt_Click(object sender, EventArgs e)
{
try
{
string tartalom = await kliens.GetStringAsync("https://pelda.hu/adat.txt"); // várakozás szál nélkül
txtTartalom.Text = tartalom;
}
catch (HttpRequestException ex)
{
tsslUzenet.Text = "Hiba a letöltéskor: " + ex.Message;
}
}
// ROSSZ: Task.Run az I/O köré: fölösleges szálat köt le a várakozás idejére
// string s = await Task.Run(() => File.ReadAllText(ut)); // működik, de pazarló...Async metódusok ezt használják, és ezrével indíthatók.Task.Run a számítást viszi a szálkészlet szálára. Minden CPU-mag egyszerre egy számítást végez, ezért a Task.Run a felületet védi, nem gyorsítja a számítást (a gyorsításhoz a számítás párhuzamosítása kell: Parallel.For).await után írd vissza.HttpClient példány az egész programban újrahasznosítható (static readonly); minden híváshoz új példányt létrehozni kimeríti a hálózati kapcsolatokat. Hálózatnál mindig számolj kivétellel (HttpRequestException, TaskCanceledException időtúllépésnél).Jósolj!
A Task.Run(() => lblSzam.Text = "kész") lambda közvetlenül írja egy címke szövegét. Mi történik?
Jóslat ellenőrzése
InvalidOperationException (cross-thread): a lambda a szálkészlet egy szálán fut, ahonnan a vezérlő nem érhető el (11.3 lecke). A Task.Run-on belül ne érj vezérlőt: az eredményt add vissza, és az await után (a felületi szálon) írd ki.
Kivételek az async világban
private async Task<int> HibasAsync()
{
await Task.Delay(300);
throw new InvalidOperationException("Valami elromlott."); // a kivétel a Task-ba kerül (Faulted)
}
// 1. await: a kivétel a hívónál „kiszabadul”, ott elkapható; try–catch a megszokott módon
private async void btnProba_Click(object sender, EventArgs e)
{
try
{
int x = await HibasAsync();
}
catch (InvalidOperationException ex)
{
tsslUzenet.Text = "Hiba: " + ex.Message; // ide kerül
}
}
// 2. .Result / .Wait(): a kivételt AggregateException-be csomagolja (belső hiba: InnerException)
try { int y = HibasAsync().Result; }
catch (AggregateException ae) { string uzenet = ae.InnerException.Message; }
// 3. async void: a kivétel NEM fogható el a hívónál, a programot összeomlaszthatja
private async void VeszelyesAsync()
{
await Task.Delay(100);
throw new Exception("ezt senki nem kapja el"); // a felületi szálon kezeletlen kivétel: összeomlás
}
// 4. elvesztett Task: nincs await, a kivétel „elnyelődik”, a hiba észrevétlen marad
Task.Run(() => { throw new Exception("senki nem várja"); }); // hol a hiba? sehol: némaawait a hibás Task kivételét újra eldobja a hívó helyén: a try–catch a megszokott módon működik, és az eredeti kivételtípust adja (nem csomagolt). Ezért az await kényelmesebb a .Result-nál..Result, .Wait() az AggregateException csomagolóba teszi a kivételt (egy Task több kivételt is hordozhat): az InnerException az eredeti. Egy újabb ok a blokkoló hívások kerülésére.async void metódusból kilépő kivétel a felületi szál SynchronizationContext kezelőjéhez kerül, és kezeletlen kivételként a programot leállíthatja. Ezért csak eseménykezelő lehet async void, és az eseménykezelő törzsét try–catch védje.Task await nélkül „elfelejtve” indítása a hibákat elveszti. Ha szándékosan indítasz háttérfeladatot, a hibát is kezeld (naplózás a feladat try–catch blokkjában), különben a program csendben hibás lesz.Jósolj!
Az eseménykezelő try { await HibasAsync(); } catch (Exception ex) { ... } kódja kezeli a hibát. Melyik szálon és mikor fut a catch blokk?
Jóslat ellenőrzése
A felületi szálon, 300 ms után (amikor a Task hibásan elkészült): az await ott dobja újra a kivételt, ahol a folytatás fut. A catch-ben a vezérlők közvetlenül elérhetők (üzenet, MessageBox).
Több feladat: WhenAll és WhenAny
private async Task<string> LetoltAsync(string nev, int ms)
{
await Task.Delay(ms); // a „letöltés”
return nev + " kész";
}
private async void btnOsszehasonlit_Click(object sender, EventArgs e)
{
Stopwatch sw = Stopwatch.StartNew();
// 1. EGYMÁS UTÁN: 1000 + 1500 + 2000 = ~4500 ms
string a = await LetoltAsync("A", 1000);
string b = await LetoltAsync("B", 1500);
string c = await LetoltAsync("C", 2000);
lblEgymasUtan.Text = sw.ElapsedMilliseconds + " ms";
// 2. EGYSZERRE: a három Task egyszerre indul, a leghosszabb dönt: ~2000 ms
sw.Restart();
Task<string> ta = LetoltAsync("A", 1000); // await NÉLKÜL: elindul, nem vár rá
Task<string> tb = LetoltAsync("B", 1500);
Task<string> tc = LetoltAsync("C", 2000);
string[] eredmenyek = await Task.WhenAll(ta, tb, tc); // megvárja mindhármat; a kivétel(ek) itt kerülnek elő
lblEgyszerre.Text = sw.ElapsedMilliseconds + " ms";
// 3. WhenAny: az első elkészült, például időtúllépéshez
Task<string> lassu = LetoltAsync("Lassú", 10000);
Task elso = await Task.WhenAny(lassu, Task.Delay(3000));
if (elso != lassu) { lblUzenet.Text = "Időtúllépés: 3 másodperc alatt nem készült el."; }
}await a futási időket összeadja (4,5 mp). Ha a feladatok függetlenek, indítsd el őket await nélkül, majd WhenAll várja meg: a futási idő a leghosszabb (2 mp). Az I/O-feladatoknál ez nagy nyereség.Task-ok közül több hibás, az await Task.WhenAll(...) az elsőt dobja újra; az összeset a WhenAll Task Exception tulajdonsága (AggregateException) adja. A hibás feladat nem állítja le a többit.WhenAny(feladat, Task.Delay(ms)) az időtúllépés klasszikus mintája: ha a Delay előbb készül el, lejárt az idő. (A feladat ettől még fut tovább: a tényleges megszakításhoz CancellationToken kell, 11.8 lecke.)SemaphoreSlim).Jósolj!
A ta, tb, tc változók Task-jait (1000, 1500, 2000 ms) await nélkül indítod, majd egymás után várod: await ta; await tb; await tc;. Mennyi ideig tart összesen?
Jóslat ellenőrzése
Kb. 2000 ms: mindhárom Task az indításkor egyszerre elkezdett futni; a ta várása 1000 ms, utána a tb már fél másodpercen belül kész, és a tc 2000 ms-nál. A sorrendben történő várás nem sorosítja őket, mert már elindultak. (A WhenAll a hibakezelés miatt áttekinthetőbb.)
Mit tegyél ide: az aszinkron laborok: a prímkeresés eredménye, a két időmérés (egymás után ~4,5 mp, egyszerre ~2 mp) és a hibakezelés üzenete.Javasolt fájlnév: winforms-11-5-aszinkron.webp · Alt szöveg: Az aszinkron laborok 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 mégis lefagy az async metódusban
private async void btn_Click(object sender, EventArgs e)
{
long db = PrimekSzama(50000000); // nincs await: a felületi szálon fut
}Hiba és javítás
Az async önmagában nem indít szálat. A számítást await Task.Run(() => PrimekSzama(...)) hívással vidd a háttérbe.
2. A program összeomlik, nem fogjuk el a kivételt
private async void Betolt() { await HibasAsync(); }
// hívás: try { Betolt(); } catch { } // nem fogja elHiba és javítás
Az async void kivétele nem jut el a hívóhoz. Használj async Task metódust, és await-tel hívd (try { await BetoltAsync(); } catch ...).
3. InvalidOperationException a Task.Run-ban
await Task.Run(() => { lblAllapot.Text = "Dolgozom"; Szamol(); });Hiba és javítás
A lambda háttérszálon fut: a vezérlő nem érhető el. Az állapotszöveget a Task.Run előtt (vagy után) írd, a felületi szálon.
4. A gomb kétszer indítja a munkát
Gyors dupla kattintásra két feldolgozás fut egyszerre. Az Ok és javítás
await alatt a felület él, és a kezelő újra hívható. Tiltsd le a gombot a futás idejére (try–finally).
5. Nincs await a hívásnál
MentAsync(); // az await hiányzik: a hiba elvész, és a program a mentés vége előtt halad tovább
Kilep();Hiba és javítás
await MentAsync(); (a hívó is async). A figyelmeztetés (CS4014) jelzi: a Visual Studio szól, ha egy Task-ot elfelejtenek várni.
6. Task.Run az I/O köré
string s = await Task.Run(() => kliens.GetStringAsync(url).Result); // két hiba egyszerreHiba és javítás
Fölösleges szálat köt le, és .Result-tal blokkol. Közvetlenül: string s = await kliens.GetStringAsync(url);.
Mini projekt: aszinkron laborok
Készíts programot négy füllel, amely az async/await alapeseteit mutatja:
- Felület:
TabControlnégy füllel (Számolás, Egymás után / egyszerre, Hibák, Időtúllépés); minden fül fölött egy mozgó négyzet és a legnagyobb késés (11.3–11.4 lecke késésmérője); állapotsor. - Számolás fül:
nudHatar(a keresés határa, 1 000–20 000 000), Számol (felületi szálon) és Számol (await Task.Run) gombok; az eredmény (a prímek száma) és az eltelt idő; a felületi szálas változatnál az animáció áll, az aszinkronnál fut; a gomb tiltott futás közben; a bemenetet aTask.Runelőtt olvasod ki. - Egymás után / egyszerre fül: a három „letöltés” (
LetoltAsync, 1000, 1500, 2000 ms): Egymás után (~4500 ms), Egyszerre (WhenAll) (~2000 ms) gombok; az idők kiírva; aTask-ok eredményei (string[]) a listában. - Hibák fül: await + try–catch (a hiba üzenete az állapotsorban), .Result + AggregateException (az
InnerExceptionüzenete), Elfelejtett Task (a hibaContinueWith-tel vagy a feladaton belüli naplózással észlelve), és a WhenAll több hibával (az összes kivétel felsorolva azAggregateExceptionInnerExceptionslistájából). - Időtúllépés fül: a „lassú letöltés” (10 mp) és a
WhenAny(feladat, Task.Delay(3000)): Időtúllépés üzenet; a lassú feladat tovább fut (a naplóban látszik); egy Gyors letöltés (1 mp) az időn belül elkészül. - Szálak: minden eredménynél kiírod a szál azonosítóját: a
Task.Runlambdában (munkaszál), azawaitelőtt és után (a felületi szál ugyanaz). - Hibatűrés: minden eseménykezelő
try–catch–finally; a Form bezárása futó feladat mellett nem okoz kivételt (IsDisposedellenőrzés az await után). - Teszteld: kis és nagy határ a prímkeresésnél, dupla kattintás, az egymás után / egyszerre idők, a hibák mindegyike, időtúllépés, a mozgó négyzet minden műveletnél (a késésmérő értéke).
Megoldás megnyitása (próbáld ki előbb önállóan!)
A központi rész a Task.Run + await számolás, az egyszerre indított Task-ok és a hibák kezelése.
private Stopwatch meroOra = Stopwatch.StartNew();
private long utolsoTick = 0, legnagyobb = 0;
private int negyzetX = 0, irany = 4;
private int felulet;
private void Form1_Load(object sender, EventArgs e)
{
felulet = Environment.CurrentManagedThreadId;
tmrMero.Interval = 50; tmrMero.Start();
tmrAnim.Interval = 20; tmrAnim.Start();
}
private void tmrMero_Tick(object sender, EventArgs e)
{
long most = meroOra.ElapsedMilliseconds;
long keses = most - utolsoTick - tmrMero.Interval;
utolsoTick = most;
if (keses > legnagyobb) { legnagyobb = keses; }
lblKeses.Text = "Legnagyobb késés: " + legnagyobb + " ms";
}
private void tmrAnim_Tick(object sender, EventArgs e)
{
negyzetX += irany;
if (negyzetX < 0 || negyzetX > pnlAnim.Width - 30) { irany = -irany; negyzetX += irany; }
pnlNegyzet.Left = negyzetX;
}
private void Naploz(string s) { lstNaplo.Items.Insert(0, DateTime.Now.ToString("HH:mm:ss") + " " + s); }
// ---- számolás ----
private long Primek(int hatar)
{
long db = 0;
for (int n = 2; n <= hatar; n++)
{
bool prim = true;
for (int d = 2; (long)d * d <= n; d++) { if (n % d == 0) { prim = false; break; } }
if (prim) { db++; }
}
return db;
}
private void btnFeluleti_Click(object sender, EventArgs e)
{
Stopwatch sw = Stopwatch.StartNew();
long db = Primek((int)nudHatar.Value); // a felületi szálon: fagy
lblEredmeny.Text = "Prímek: " + db + " (" + sw.ElapsedMilliseconds + " ms, felületi szál)";
}
private async void btnAszinkron_Click(object sender, EventArgs e)
{
btnAszinkron.Enabled = false;
btnFeluleti.Enabled = false;
lblEredmeny.Text = "Számolok..."; // látszik: a felület él
int hatar = (int)nudHatar.Value; // a bemenetet A FELÜLETI SZÁLON olvassuk ki
try
{
Stopwatch sw = Stopwatch.StartNew();
int munkaSzal = 0;
long db = await Task.Run(() => { munkaSzal = Environment.CurrentManagedThreadId; return Primek(hatar); });
if (IsDisposed) { return; } // közben bezárhatták
lblEredmeny.Text = "Prímek: " + db + " (" + sw.ElapsedMilliseconds + " ms, munkaszál: " + munkaSzal + ", folytatás szála: " + Environment.CurrentManagedThreadId + ", felületi: " + felulet + ")";
}
catch (Exception ex) { tsslUzenet.Text = "Hiba: " + ex.Message; }
finally
{
if (!IsDisposed) { btnAszinkron.Enabled = true; btnFeluleti.Enabled = true; }
}
}
// ---- egymás után / egyszerre ----
private async Task<string> LetoltAsync(string nev, int ms)
{
await Task.Delay(ms);
return nev + " kész";
}
private async void btnEgymasUtan_Click(object sender, EventArgs e)
{
btnEgymasUtan.Enabled = false;
try
{
Stopwatch sw = Stopwatch.StartNew();
string a = await LetoltAsync("A", 1000);
string b = await LetoltAsync("B", 1500);
string c = await LetoltAsync("C", 2000);
Naploz("Egymás után: " + sw.ElapsedMilliseconds + " ms (" + a + ", " + b + ", " + c + ")");
}
finally { btnEgymasUtan.Enabled = true; }
}
private async void btnEgyszerre_Click(object sender, EventArgs e)
{
btnEgyszerre.Enabled = false;
try
{
Stopwatch sw = Stopwatch.StartNew();
Task<string> ta = LetoltAsync("A", 1000), tb = LetoltAsync("B", 1500), tc = LetoltAsync("C", 2000); // mind elindul
string[] eredmenyek = await Task.WhenAll(ta, tb, tc);
Naploz("Egyszerre (WhenAll): " + sw.ElapsedMilliseconds + " ms (" + string.Join(", ", eredmenyek) + ")");
}
finally { btnEgyszerre.Enabled = true; }
}
// ---- hibák ----
private async Task<int> HibasAsync(string uzenet)
{
await Task.Delay(300);
throw new InvalidOperationException(uzenet);
}
private async void btnAwaitHiba_Click(object sender, EventArgs e)
{
try { await HibasAsync("await hiba"); }
catch (InvalidOperationException ex) { tsslUzenet.Text = "await + catch: " + ex.Message + " (szál: " + Environment.CurrentManagedThreadId + ")"; }
}
private async void btnResultHiba_Click(object sender, EventArgs e)
{
// a .Result BLOKKOLNA a felületi szálon: ezért háttérszálról hívjuk a demóhoz
string uzenet = await Task.Run(() =>
{
try { return HibasAsync("result hiba").Result.ToString(); }
catch (AggregateException ae) { return "AggregateException → InnerException: " + ae.InnerException.Message; }
});
tsslUzenet.Text = uzenet;
}
private async void btnWhenAllHiba_Click(object sender, EventArgs e)
{
Task<int> t1 = HibasAsync("első hiba"), t2 = HibasAsync("második hiba"), t3 = Task.FromResult(3);
Task osszes = Task.WhenAll(t1, t2, t3);
try { await osszes; }
catch (Exception) // az await csak az ELSŐ kivételt dobja újra
{
AggregateException ae = osszes.Exception;
Naploz("WhenAll: " + ae.InnerExceptions.Count + " hiba: " + string.Join("; ", ae.InnerExceptions.Select(x => x.Message)));
}
}
private void btnElfelejtett_Click(object sender, EventArgs e)
{
// szándékosan nem várjuk: a hibát a ContinueWith észleli és naplózza (különben elveszne)
HibasAsync("elfelejtett").ContinueWith(t =>
{
try { BeginInvoke(new Action(() => Naploz("Az elfelejtett Task hibája: " + t.Exception.InnerException.Message))); }
catch (InvalidOperationException) { }
}, TaskContinuationOptions.OnlyOnFaulted);
}
// ---- időtúllépés ----
private async void btnIdotullepes_Click(object sender, EventArgs e)
{
Task<string> lassu = LetoltAsync("Lassú", 10000);
Task elso = await Task.WhenAny(lassu, Task.Delay(3000));
if (elso != lassu) { Naploz("IDŐTÚLLÉPÉS: 3 másodperc alatt nem készült el (a feladat tovább fut)"); }
else { Naploz("Elkészült: " + lassu.Result); } // a lassu már kész: a Result nem blokkol
}
private async void btnGyors_Click(object sender, EventArgs e)
{
Task<string> gyors = LetoltAsync("Gyors", 1000);
Task elso = await Task.WhenAny(gyors, Task.Delay(3000));
Naploz(elso == gyors ? "Időn belül elkészült: " + gyors.Result : "Időtúllépés");
}Mit tegyél ide: a kész aszinkron laborok az időmérésekkel és a naplóval.Javasolt fájlnév: winforms-11-5-kesz.webp · Alt szöveg: A kész aszinkron laborok
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. Aszinkron mentés
A mentés gombja a jegyzetet aszinkron írja fájlba (10.9 lecke). Mi változik a kódban? A Egy lehetséges megoldás
File.WriteAllText helyett await File.WriteAllTextAsync(...); az eseménykezelő async void; a gomb tiltva a mentés alatt; a try–catch az await körül; a bemenetet (a szöveget) előbb olvasod ki a vezérlőből.
2. Sorozatos feldolgozás
10 fájlt kell egymás után feldolgozni, a felület közben él, és az állapotsor mutatja a haladást. Hogyan? Egy Egy lehetséges megoldás
for ciklus az async eseménykezelőben: await a feldolgozásra (Task.Run vagy ...Async), a ciklus testében a haladás frissítése (az await után a felületi szálon vagyunk).
3. Gyorsítótár
Egy async metódus az eredményt gyorsítótárba teszi. Ha már megvan, mit adjon vissza?Egy lehetséges megoldás
Task.FromResult(megvan): kész Task, nincs aszinkron költség. Az async metódus return megvan; sorral is jó.
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. Task vagy Task<T>?
Melyiket adja vissza az (a) async metódus: (a) fájlt ment, nincs eredmény; (b) az átlagot számolja?Megoldás megnyitása
async Task; (b) async Task<double>.
2. Hol fut?
Melyik szálon fut: (a) (a) a felületi szálon; (b) a szálkészlet egy háttérszálán.await után a felületi kódban; (b) a Task.Run lambdája?Megoldás megnyitása
Közepes szint
3. Task.Run kell?
Kell-e (a) Nem: Task.Run: (a) fájl olvasása; (b) 10 millió elem rendezése?Megoldás megnyitása
ReadAllTextAsync (I/O); (b) igen: CPU-igényes számítás, await Task.Run(() => lista.Sort()).
4. Hibakeresés
Az async void metódus kivétele összeomlasztja a programot. Hogyan javítod?Megoldás megnyitása
async Task visszatérési típus, és az await körül try–catch a hívónál; eseménykezelőben az egész törzs try–catch blokkban.
Kihívás szint
5. Korlátozott párhuzam
100 fájlt kell letölteni, de egyszerre legfeljebb 5-öt. Mit használsz? A Megoldás megnyitása
SemaphoreSlim(5): minden feladat előtt await sem.WaitAsync(), utána finally-ban sem.Release(); az összes feladat Task.WhenAll alatt.SemaphoreSlim kapu = new SemaphoreSlim(5);async Task LetoltEgyAsync(string url){ await kapu.WaitAsync(); try { await kliens.GetStringAsync(url); } finally { kapu.Release(); }}await Task.WhenAll(urlek.Select(LetoltEgyAsync));
6. Folytatás szála
Hogyan ellenőrzöd, hogy az Az await utáni kód a felületi szálon fut?Megoldás megnyitása
Environment.CurrentManagedThreadId összehasonlítása a Load-ban eltárolt felületi szál azonosítóval, vagy a vezérlő InvokeRequired tulajdonsága (hamis: a felületi szálon vagyunk).
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. Egy folyamatban lévő vagy kész munkát képviselő objektum (nem szál); lehet kész, hibás vagy megszakított. Megállítja a metódust, visszaadja a vezérlést a hívónak, és a Nem; csak az CPU-igényes számításnál; I/O-hoz a beépített Nem várható, a kivétele nem fogható el a hívónál; csak eseménykezelő lehet. Egyszerre indított feladatok közös várását: a futási idő a leghosszabb, nem az összeg. A lambda háttérszálon fut, a vezérlő csak a felületi szálról érhető el.1. Mi a Task?
2. Mit tesz az await?
Task elkészülte után a felületi szálon folytatja.3. Indít-e szálat az async?
await használatát teszi lehetővé.4. Mikor kell a Task.Run?
...Async metódusok valók.5. Mi a baj az async void-dal?
6. Mit ad a WhenAll?
7. Miért nem működik a Task.Run-ban a vezérlő?
Mit tudsz már?
- Érted a
Task,Task<T>fogalmát és azasync/awaitszabályait. - Számításigényes munkát
Task.Run-nal, I/O-t beépített aszinkron metódusokkal oldasz meg. - Kezeled a kivételeket az
awaitkörül, és kerülöd azasync voidés az elfelejtettTaskcsapdáit. - Több feladatot egyszerre indítasz és vársz (
WhenAll,WhenAny). - Tudod, hogy az await utáni kód a felületi szálon folytatódik.
Következő lecke: Háttérben végzett műveletek
Hosszabb háttérmunka szervezése: BackgroundWorker, Task.Run haladóbb használata, párhuzamos feldolgozás és a nagy fájl keresésének átírása.



