Grafikus C# – Windows Forms tanfolyam › 11.3 lecke

Windows Forms · 11.3 lecke · szálak

UI-szál és a felület lefagyása

Minden Windows Forms program tapasztalt már lefagyást: egy gombnyomás után az ablak nem mozdítható, nem rajzolódik újra, a címsorban megjelenik a (Nem válaszol) felirat. Ez nem a számítógép „lassúsága”, hanem a program szerkezetéből következik: a felület minden eseményét egyetlen szál dolgozza fel, a felületi szál (UI-szál, főszál). Ha ez a szál egy eseménykezelőben hosszú munkát végez, nincs ideje rajzolni és az egérre reagálni. Ebben a leckében megérted az üzenetsort, a lefagyás pontos okát, a vezérlők szálhoz kötöttségét, az Application.DoEvents csapdáit, és megteszed az első lépést a háttérszál felé.

  • kb. 100 perc
  • közepes–haladó szint
  • szálak
Anime stílusú fiú és lány egy C# programozási feladatot beszél meg

A lecke végére

  • elmagyarázod az üzenetsor (message loop) működését és a felületi szál szerepét;
  • tudod, miért fagy le az ablak egy hosszú eseménykezelő alatt, és felismered a tüneteit;
  • ismered a szabályt: a vezérlők csak a létrehozó szálról érhetők el (InvalidOperationException, InvokeRequired);
  • érted az Application.DoEvents működését és az újrabelépés (reentrancy) veszélyét;
  • mérni tudod a felület késését (az időzítők közti eltérés);
  • elindítasz egy háttérszálat (Thread), és tudod, mit szabad és mit nem szabad benne.

Mielőtt elkezded

Ehhez a leckéhez az 5.1 lecke (események), a 11.1–11.2 lecke (Timer, Stopwatch, folyamatjelző) szükséges. Szálakról korábban nem kellett tanulnod.

11.2 ProgressBar →

Az üzenetsor és a felületi szál

A Windows a felhasználói eseményeket (kattintás, billentyű, újrarajzolás kérése, időzítő jelzése) üzenetként egy sorba teszi minden ablakhoz. A programod Main metódusában az Application.Run(new Form1()) egy végtelen ciklust indít, amely az üzeneteket sorban kiveszi, és meghívja a megfelelő eseménykezelőt. Ez a ciklus és minden eseménykezelő ugyanazon a szálon fut:

A felületi szál ciklusa (egyszerűsítve)ábra
Application.Run(...)
ismételd:
várj, amíg érkezik üzenet
kattintás? → hívd a Click eseménykezelőt
újrarajzolás? → hívd a Paint-et
időzítő jelzés? → hívd a Tick-et
billentyű? → hívd a KeyDown-t
bezárás? → hívd a FormClosing-ot
(közben EGY üzenetet dolgozol fel; a többi a sorban VÁR)
Form1.cs (using System.Threading;)C#
private int felületiSzal;

private void Form1_Load(object sender, EventArgs e)
{
felületiSzal = Environment.CurrentManagedThreadId; // a felületi szál azonosítója
lblSzal.Text = "Felületi szál: " + felületiSzal;
}

private void btnTeszt_Click(object sender, EventArgs e)
{
// minden eseménykezelő ugyanazon a szálon fut
lstNaplo.Items.Add("Click: szál " + Environment.CurrentManagedThreadId); // ugyanaz a szám
}

private void tmr_Tick(object sender, EventArgs e)
{
lstNaplo.Items.Add("Tick: szál " + Environment.CurrentManagedThreadId); // ez is ugyanaz
}
Egy szál, sok üzenetA Click, Paint, Tick, TextChanged és minden más felületi esemény a felületi szálon fut, egymás után: sosem egyszerre. Emiatt nem kell zárakat használnod, amíg a felületi szálon maradsz; ez a Windows Forms egyik kényelme.
Paint is üzenetAz ablak újrarajzolása is üzenet. Ha a felületi szál foglalt, a Paint üzenet sorban áll: az ablak üres, szürke vagy tele van „szellem-nyomokkal”, amíg a szál szabad lesz.
A sor várakozikAmíg egy eseménykezelő fut, a sorban álló üzenetek (kattintás, mozgatás, bezárás) nem dolgozódnak fel. Ha az ablak 5 másodpercnél hosszabb ideig nem veszi ki az üzeneteket, a Windows (Nem válaszol) jelzést tesz a címsorba.
A szál azonosítójaAz Environment.CurrentManagedThreadId az éppen futó szál száma. Kísérletezéshez hasznos: kiírva látod, melyik kód melyik szálon fut.

Jósolj!

A Click és a Tick eseménykezelő is kiírja a szál azonosítóját. Egyezik-e a két szám?

Jóslat ellenőrzése

Igen: mindkettő a felületi szálon fut (a Windows.Forms.Timer a felületi szálon küldi a Tick eseményt, 11.1 lecke). A System.Timers.Timer.Elapsed kezelője ezzel szemben más számot mutatna: az másik szálon fut.

Miért fagy le az ablak?

Form1.csC#
private void btnSzamol_Click(object sender, EventArgs e)
{
btnSzamol.Enabled = false;
lblAllapot.Text = "Számolok..."; // A SZÖVEG NEM FOG MEGJELENNI: a felület szál nem jut rajzoláshoz

long db = 0;

for (long n = 2; n < 60000000; n++) // hosszú számítás a felületi szálon
{
if (Prim(n)) { db++; }
}

lblAllapot.Text = "Kész: " + db; // csak ekkor frissül mindkét szöveg (egyszerre)
btnSzamol.Enabled = true;
}
A szöveg miért nem jelenik meg?A lblAllapot.Text = ... csak módosítja a vezérlő adatát és kér egy újrarajzolást (Paint üzenet). A tényleges rajzolás az üzenetsor feldolgozásakor történik. Mivel a felületi szál a ciklusban van, a Paint csak a kezelő végén fut le: a „Számolok...” szöveg sosem látszik, az ablak „kész”-re ugrik.
Mi látszik a felhasználónak?Az ablak nem mozdítható, a gombok nem reagálnak, a Mégse sem használható (az is csak üzenet), 5 másodperc után (Nem válaszol). A programot a Feladatkezelővel lehet leállítani, és a felhasználó elveszti a munkáját.
Nem a számítás a bűnA számítás lehet hosszú: a hiba az, hogy a felületi szálon fut. A megoldás: a munkát másik szálra tenni (11.5–11.6 lecke), vagy kis darabokra osztani (11.2 lecke).
A küszöbKb. 100 ms alatt a felhasználó nem érzékeli a késést; 100 ms–1 s közt „lassúnak”, 1 s felett akadozónak érzi; 5 s felett a Windows nem válaszolónak jelzi. Az eseménykezelő ezért legyen gyors (néhány tízezred másodperc).

Jósolj!

A felhasználó a hosszú számítás közben kétszer rákattint a Bezár (X) gombra. Mi történik?

Jóslat ellenőrzése

A kattintások a sorba kerülnek, és csak a számítás vége után dolgozódnak fel (a FormClosing akkor fut le). A Windows ugyanakkor felajánlja: Program bezárása (a folyamat erőszakos leállítása), ami az adatvesztés kockázatával jár.

A szabály: a vezérlők a létrehozó szálhoz tartoznak

Ha a számítást másik szálra tesszük (a megoldás), új szabály lép életbe: a vezérlők csak azon a szálon érhetők el, amelyik létrehozta őket (a felületi szálon). A Windows Forms vezérlők nem szálbiztosak: két szál egyidejű módosítása megsérthetné a belső állapotukat. A hibát a keretrendszer észleli:

Form1.cs (using System.Threading;)C#
private void btnSzal_Click(object sender, EventArgs e)
{
Thread szal = new Thread(() =>
{
long db = PrimekSzama(60000000); // hosszú munka: a felület közben él

// lblAllapot.Text = "Kész: " + db; // InvalidOperationException: másik szálról nem érhető el!

// helyesen: a felületi szálra „küldjük” a módosítást
this.BeginInvoke(new Action(() => lblAllapot.Text = "Kész: " + db));
});

szal.IsBackground = true; // a program bezárásakor ne tartsa életben
szal.Start();
}

// ellenőrzés: melyik szálon vagyunk?
private void UjCimke(string szoveg)
{
if (lblAllapot.InvokeRequired) // igaz, ha NEM a vezérlő szálán hívták
{
lblAllapot.BeginInvoke(new Action<string>(UjCimke), szoveg); // ugyanezt a metódust a felületi szálon újra hívja
return;
}

lblAllapot.Text = szoveg;
}
InvalidOperationExceptionA vezérlő másik szálról való elérése általában InvalidOperationException kivételt dob (Cross-thread operation not valid). Régebbi .NET Framework alatt ez csak hibakereséskor (a Visual Studióban) jelentkezhetett, éles futásnál csendben tönkretehette a programot: a hibát mindig javítani kell.
InvokeRequiredA vezérlő InvokeRequired tulajdonsága igaz, ha az aktuális szál nem a vezérlőé. Ilyenkor az Invoke (megvárja) vagy BeginInvoke (nem vár) a megadott kódot a felületi szálon futtatja. Részletesen a 11.7 leckében.
A tiltó trükk, amit tilosA Control.CheckForIllegalCrossThreadCalls = false kikapcsolja a hibajelzést, de nem szünteti meg a problémát: versenyhelyzeteket, véletlenszerű hibákat okoz. Soha ne használd.
Mit szabad a háttérszálban?Bármit, ami nem vezérlő: számítás, fájl- és hálózati művelet, adatszerkezetek (saját listák). A vezérlőket csak a felületi szálon éred el; az eredményt oda kell „hazaküldeni”.

Jósolj!

A háttérszál a számítás közben ötször közvetlenül írja a progressBar1.Value értékét, kivétel nélkül (mert a CheckForIllegalCrossThreadCalls hamis). Mi a veszély?

Jóslat ellenőrzése

A vezérlő belső állapotát két szál egyszerre módosíthatja: ritkán, véletlenszerűen megjelenő hibák (lefagyás, hibás rajz, handle-hibák). Mivel „általában működik”, a hiba nehezen reprodukálható és nehezen javítható. A helyes út az Invoke/BeginInvoke, vagy a Progress<T> (11.7 lecke).

Az Application.DoEvents és az újrabelépés

Az Application.DoEvents() a hosszú ciklus közben ideiglenesen feldolgozza a sorban álló üzeneteket: az ablak újrarajzolódik, a gombok reagálnak. Régi programokban (és a mi 10.2 leckénk vészmegoldásában) gyakori. Csak vészmegoldás, mert maga a ciklus továbbra is a felületi szálon fut, és új hibát hoz: az újrabelépést:

Form1.csC#
private int futo = 0;

private void btnMunka_Click(object sender, EventArgs e)
{
futo++;

if (futo > 1) { lstNaplo.Items.Add("ÚJRABELÉPÉS: a kezelő már fut, és újra elindult (" + futo + ". példány)"); }

for (int i = 0; i < 50; i++)
{
Thread.Sleep(100); // a „munka”
Application.DoEvents(); // a felület frissül, DE új Click is feldolgozódhat!
}

futo--;
}
ÚjrabelépésA DoEvents közben a sorban álló új kattintás is feldolgozódik: ugyanaz a kezelő még egyszer elindul, a futó példány „alatt”. Két párhuzamos (egymásba ágyazott) munka keletkezik, az adatok összekeverednek, az állapot (futo) hibás.
További veszélyekA DoEvents közben a FormClosing is lefuthat (a Form bezárul, miközben a munka a vezérlőket használja), vagy más események módosíthatják az adatokat, amelyeket a ciklus épp feldolgoz.
VédekezésFutás alatt tiltsd le a Indít gombot (Enabled = false), ellenőrizz egy futo jelzőt, és kezeld a bezárást. A valódi megoldás nem a DoEvents finomítása, hanem a háttérszál.
Mi a jó benne?Egyszerű, és kisebb programoknál, rövid műveletnél „elég jó”. Éles programban a háttérmunka a helyes: az async/await (11.5) és a Task.Run (11.6) ezt a problémát elegánsan megoldja.

Jósolj!

A fenti kezelő futása közben (5 másodperc) a felhasználó háromszor kattint az Indít gombra. A gomb nincs letiltva. Hányszor indul el a ciklus, és mikor ér véget a legelső?

Jóslat ellenőrzése

Összesen négyszer (az eredeti + három újrabelépés); mivel egymásba ágyazódnak, a legelső csak utoljára fejeződik be (a veremben a legalsó): a futási idő megsokszorozódik, és a naplóban az ÚJRABELÉPÉS üzenetek jelennek meg.

📷 Képernyőkép helye

Mit tegyél ide: a fagyás-labor: a négy változat gombjai, a mozgó négyzet, a késésmérő értéke és a napló az ÚJRABELÉPÉS üzenetekkel.Javasolt fájlnév: winforms-11-3-fagyas-labor.webp · Alt szöveg: A fagyás-labor 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. Az állapotszöveg nem jelenik meg a művelet közben

lblAllapot.Text = "Dolgozom...";
HosszuMunka(); // ugyanazon a szálon
Hiba és javítás

A felületi szál foglalt: a rajzolás a kezelő után történik. A munkát háttérszálra kell tenni (vagy darabolni); az Application.DoEvents csak vészmegoldás.

2. InvalidOperationException a háttérszálban

new Thread(() => { lstNaplo.Items.Add("kész"); }).Start();
Hiba és javítás

A ListBox másik szálról nem érhető el. Használj BeginInvoke / Invoke metódust.

3. A programot nem lehet bezárni

Thread szal = new Thread(Munka);
szal.Start(); // előtér-szál: a program addig fut, amíg a szál él
Hiba és javítás

Az előtér-szál (alapértelmezett) életben tartja a folyamatot a Form bezárása után is. Állítsd IsBackground = true értékre, és adj lehetőséget a megszakításra (11.8 lecke).

4. A gomb kétszer indítja a munkát

A gyors dupla kattintás két párhuzamos (vagy egymásba ágyazott) feldolgozást indít.

Ok és javítás

Nincs letiltva a gomb, és nincs futo jelző. A kezelő első sora: btnIndit.Enabled = false (és a végén true, finally blokkban).

5. CheckForIllegalCrossThreadCalls = false

Control.CheckForIllegalCrossThreadCalls = false;   // „megoldás”
Hiba és javítás

Elfedi, nem javítja a hibát: a program véletlenszerűen összeomolhat. Töröld, és használj Invoke/BeginInvoke vagy Progress<T> megoldást.

6. A DoEvents után eltűnt vezérlőt érünk el

A ciklus NullReferenceException vagy ObjectDisposedException kivételt dob.

Ok és javítás

A DoEvents közben a felhasználó bezárta az ablakot. Ellenőrizd az IsDisposed értéket, vagy használj háttérszálat és megszakítást (11.8 lecke), és tiltsd a bezárást, amíg a munka fut.

Mini projekt: fagyás-labor késésmérővel

Készíts kísérleti programot, amely négyféle módon futtat ugyanazt a „munkát”, és méri, mennyire marad élő a felület:

  1. Felület: négy gomb (1. Blokkoló, 2. DoEvents, 3. Háttérszál, 4. Darabolt (Timer)); nudMp (a munka hossza, 1–10 mp); egy animáció: a pnlAnim panelen egy négyzet 20 ms-onként mozog (tmrAnim); egy txtGepel szövegmező (próbáld a munka közben gépelni); lblKeses, lblSzal; lstNaplo.
  2. Késésmérő: a tmrMero (Interval = 50) minden Tick-nél megméri az előző Tick óta eltelt időt (Stopwatch); a legnagyobb eltérést (legnagyobb késés) kiírja; a Nullázás gomb törli. Ez a szám mutatja: mennyi ideig nem „lélegzett” a felületi szál.
  3. 1. Blokkoló: a munka (Thread.Sleep darabokban vagy egy ciklus) közvetlenül a kezelőben; a késés ≈ a munka hossza; az animáció áll, a gépelés nem látszik.
  4. 2. DoEvents: a munka 100 ms-os darabokban, közben Application.DoEvents(); az animáció mozog; a futo számláló jelzi az újrabelépést, ha többször kattintanak (a gomb szándékosan nincs letiltva); a naplóban az ÚJRABELÉPÉS sor.
  5. 3. Háttérszál: új Thread (IsBackground = true), a munka a szálon; az eredmény BeginInvoke segítségével a naplóba kerül; a szál azonosítója látszik (lblSzal: felületi és munkaszál); az animáció végig mozog; a késés kicsi.
  6. 4. Darabolt (Timer): egy Tick legfeljebb 25 ms munkát végez (11.2 lecke); az animáció mozog; a késés kicsi; megszakítható.
  7. Cross-thread kísérlet: a Közvetlen elérés szálról gomb új szálból közvetlenül módosít egy címkét; a kivétel (InvalidOperationException) üzenete a naplóba kerül (a szálból BeginInvoke segítségével), a program nem omlik össze.
  8. Biztonság: a Form bezárása futó szál mellett nem hagy „szellem” folyamatot (IsBackground); a BeginInvoke hívások ObjectDisposedException védelme.
  9. Teszteld: mind a négy változat 3 és 8 másodperccel, gépelés és ablakmozgatás közben, a legnagyobb késés értéke, dupla kattintás a DoEvents-nél, bezárás futás közben.
Megoldás megnyitása (próbáld ki előbb önállóan!)

A központi rész a késésmérő és a négyféle futtatás; a „munka” mindegyiknél ugyanaz: Munka(ms).

Form1.cs (using System.Diagnostics; using System.Threading;)C#
private int futo = 0;
private int felulet;
private Stopwatch meroOra = Stopwatch.StartNew();
private long utolsoTick = 0, legnagyobbKeses = 0;
private int negyzetX = 0, irany = 4;

private void Form1_Load(object sender, EventArgs e)
{
felulet = Environment.CurrentManagedThreadId;
lblSzal.Text = "Felületi szál: " + felulet;

tmrMero.Interval = 50;
tmrMero.Start();
tmrAnim.Interval = 20;
tmrAnim.Start();
}

// ---- késésmérő: az elvárt 50 ms-hoz képest mennyit késik a Tick ----
private void tmrMero_Tick(object sender, EventArgs e)
{
long most = meroOra.ElapsedMilliseconds;
long keses = most - utolsoTick - tmrMero.Interval;

utolsoTick = most;

if (keses > legnagyobbKeses) { legnagyobbKeses = keses; }

lblKeses.Text = "Legnagyobb késés: " + legnagyobbKeses + " ms";
}

private void btnNulla_Click(object sender, EventArgs e) { legnagyobbKeses = 0; lstNaplo.Items.Clear(); }

// ---- animáció: ha a felület szál foglalt, megáll ----
private void tmrAnim_Tick(object sender, EventArgs e)
{
negyzetX += irany;

if (negyzetX < 0 || negyzetX > pnlAnim.Width - 30) { irany = -irany; negyzetX += irany; }

pnlNegyzet.Left = negyzetX;
}

// ---- a „munka”: ugyanaz mindegyik változatnál ----
private void Munka(int ms)
{
Stopwatch sw = Stopwatch.StartNew();

while (sw.ElapsedMilliseconds < ms) { Thread.Sleep(20); }
}

private int Hossz { get { return (int)nudMp.Value * 1000; } }

private void Naploz(string s) { lstNaplo.Items.Insert(0, DateTime.Now.ToString("HH:mm:ss") + " " + s); }

// 1. blokkoló: a felület szál dolgozik
private void btnBlokkolo_Click(object sender, EventArgs e)
{
Naploz("1. Blokkoló indul (szál " + Environment.CurrentManagedThreadId + ")");
Munka(Hossz);
Naploz("1. Blokkoló kész (a felület közben nem reagált)");
}

// 2. DoEvents: a felület él, de az újrabelépés veszélyes
private void btnDoEvents_Click(object sender, EventArgs e)
{
futo++;

if (futo > 1) { Naploz("ÚJRABELÉPÉS: már fut " + (futo - 1) + " példány!"); }

Stopwatch sw = Stopwatch.StartNew();

while (sw.ElapsedMilliseconds < Hossz)
{
Thread.Sleep(20);
Application.DoEvents();

if (IsDisposed) { return; } // közben bezárhatták
}

Naploz("2. DoEvents kész (példányok: " + futo + ")");
futo--;
}

// 3. háttérszál
private void btnSzal_Click(object sender, EventArgs e)
{
int ms = Hossz;

Naploz("3. Háttérszál indul");

Thread szal = new Thread(() =>
{
int enSzalam = Environment.CurrentManagedThreadId;

Munka(ms);

try { BeginInvoke(new Action(() => Naploz("3. Háttérszál kész (munkaszál: " + enSzalam + ", felületi szál: " + felulet + ")"))); }
catch (ObjectDisposedException) { } // a Form közben bezárult
catch (InvalidOperationException) { }
});

szal.IsBackground = true;
szal.Start();
}

// 4. darabolt, időzítővel
private Stopwatch dMunka = new Stopwatch();

private void btnDarabolt_Click(object sender, EventArgs e)
{
if (tmrDarab.Enabled) { return; }

Naploz("4. Darabolt indul");
dMunka.Restart();
tmrDarab.Interval = 30;
tmrDarab.Start();
}

private void tmrDarab_Tick(object sender, EventArgs e)
{
Stopwatch keret = Stopwatch.StartNew();

while (keret.ElapsedMilliseconds < 25 && dMunka.ElapsedMilliseconds < Hossz) { Thread.Sleep(5); } // egy Tick legfeljebb ~25 ms

if (dMunka.ElapsedMilliseconds >= Hossz)
{
tmrDarab.Stop();
Naploz("4. Darabolt kész (a felület közben élt)");
}
}

// cross-thread kísérlet
private void btnCross_Click(object sender, EventArgs e)
{
Thread szal = new Thread(() =>
{
try { lblSzal.Text = "másik szálról"; }
catch (InvalidOperationException ex)
{
try { BeginInvoke(new Action(() => Naploz("Cross-thread kivétel: " + ex.Message))); }
catch (ObjectDisposedException) { }
}
});

szal.IsBackground = true;
szal.Start();
}
📷 Képernyőkép helye

Mit tegyél ide: a kész fagyás-labor a négy változat méréseivel.Javasolt fájlnév: winforms-11-3-kesz.webp · Alt szöveg: A kész fagyás-labor

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. Melyik szálon vagyok?

Írj segédmetódust, amely kiírja, hogy az aktuális kód a felületi szálon fut-e. Mit használsz?

Egy lehetséges megoldás

A felületi szál azonosítóját a Load-ban eltárolod, és összehasonlítod: Environment.CurrentManagedThreadId == felulet. Vezérlőre a InvokeRequired is megmondja.

2. Fagyás-érzékelő

A program naplózza, ha a felület több mint 200 ms-ig nem reagált. Hogyan?

Egy lehetséges megoldás

Egy 50 ms-os Timer és a Stopwatch (a késésmérő): ha a Tick-ek közti idő > 250 ms, naplózod. Éles programban a naplóba az időpont és az utolsó művelet kerülhet.

3. Biztonságos címkefrissítő

Írj Szoveget(Label l, string s) segédmetódust, amely bármelyik szálról hívható. Mi a szerkezete?

Egy lehetséges megoldás

Az InvokeRequired ellenőrzés, igaz esetén BeginInvoke a saját magát hívó metódusra; hamis esetén közvetlen értékadás. Ellenőrizd az IsHandleCreated és IsDisposed értéket is.

Form1.csC#
private void Szoveget(Label l, string s){    if (l.IsDisposed) { return; }    if (l.InvokeRequired)    {        try { l.BeginInvoke(new Action<Label, string>(Szoveget), l, s); }        catch (ObjectDisposedException) { }        catch (InvalidOperationException) { }   // a fogantyú még nincs létrehozva vagy már megszűnt        return;    }    l.Text = s;}

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. Mi a felületi szál?

Fogalmazd meg egy mondatban, mi a felületi szál feladata.

Megoldás megnyitása

Az a szál, amely az üzenetsort feldolgozza: meghívja az eseménykezelőket és rajzol; a vezérlők csak erről a szálról érhetők el.

2. Mi okozza a (Nem válaszol) jelzést?

Mi történik a háttérben, hogy a Windows ezt kiírja?

Megoldás megnyitása

A felületi szál több másodpercig nem veszi ki az üzeneteket a sorból (hosszú eseménykezelő); a Windows ezt észleli.

Közepes szint

3. Mi nem megy másik szálon?

Melyik művelet tilos a háttérszálban: (a) számítás; (b) fájl olvasása; (c) Label.Text beállítása?

Megoldás megnyitása

(c): a vezérlő csak a felületi szálról érhető el. (a) és (b) szabad.

4. IsBackground

Mi a különbség az előtér- és a háttérszál között?

Megoldás megnyitása

A program addig fut, amíg előtér-szál van; a háttérszál a program bezárásakor automatikusan leáll. UI-programban a munkaszálak általában háttérszálak.

Kihívás szint

5. Invoke és BeginInvoke

Mi a különbség, és mikor okozhat az Invoke holtpontot?

Megoldás megnyitása

Az Invoke megvárja a felületi szál végrehajtását, a BeginInvoke nem. Holtpont: ha a felületi szál épp a háttérszálra vár (például szal.Join()), a háttérszál pedig Invoke-ra vár: kölcsönösen várnak (11.4 és 11.7 lecke).

6. Hány szál?

Egy egyszerű Windows Forms program Feladatkezelőben látható szálszáma több, mint egy. Miért?

Megoldás megnyitása

A .NET futtatókörnyezet saját szálai (szemétgyűjtő, finalizer, szálkészlet) és a Windows belső szálai is megjelennek. A programod logikai felületi szála egy; a többi nem a te kódodat futtatja.

Gyors önellenőrzés

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

1. Mi az üzenetsor?

A Windows eseményeit (kattintás, rajzolás, időzítő) tároló sor; a felületi szál ciklusa dolgozza fel.

2. Miért fagy le az ablak?

A hosszú eseménykezelő foglalja a felületi szálat: nem rajzol és nem dolgozza fel a többi üzenetet.

3. Mi a küszöb a lassúság érzetéhez?

~100 ms alatt észrevétlen, ~1 s fölött akadozás, 5 s fölött (Nem válaszol).

4. Mit szabad a háttérszálban?

Számítás, fájl, hálózat, saját adatok; vezérlőt csak a felületi szálról.

5. Mit okoz a másik szálról elért vezérlő?

InvalidOperationException (Cross-thread operation not valid).

6. Mi az újrabelépés a DoEvents-nél?

A DoEvents közben az új kattintás ugyanazt a kezelőt újra elindítja a futó példány „alatt”.

7. Miért ne használd a CheckForIllegalCrossThreadCalls = false értéket?

Elfedi a hibát, versenyhelyzetet és véletlenszerű összeomlást okozhat.

Mit tudsz már?

  • Érted az üzenetsort és azt, hogy minden eseménykezelő a felületi szálon fut.
  • Tudod, miért fagy le az ablak, és mérni is tudod a felület késését.
  • Ismered a szabályt: a vezérlők csak a létrehozó szálról érhetők el.
  • Érted a DoEvents csapdáit (újrabelépés).
  • Elindítasz egy háttérszálat, és az eredményt biztonságosan hazaküldöd.

Következő lecke: Thread.Sleep és a blokkolás problémája

A blokkoló hívások (Thread.Sleep, .Wait(), .Result) hatása a felületre, a holtpont és a nem blokkoló alternatívák.