Grafikus C# – Windows Forms tanfolyam › 11.1 lecke

Windows Forms · 11.1 lecke · időzítők

Timer és Tick esemény

Az 1.8 leckében a Timer alapjait megismerted, és az 5.9 leckében a kígyójáték is rá épült. Most mélyebbre nézünk, mert az időzítőnek van néhány alattomos tulajdonsága: a Tick nem pontos, a Tick-ek számolásával mért idő elcsúszik, és a .NET-ben háromféle Timer osztály van, amelyek más szálon futnak. Megtanulod, mikor melyiket válaszd, hogyan mérsz pontos időt a Stopwatch-csal, hogyan készítesz visszaszámlálót szünettel, és hogyan szabadítod fel az időzítőt. Ez a lecke a fejezet többi részének alapja: az időzítő a legegyszerűbb módja annak, hogy a program „közben is éljen”.

  • kb. 90 perc
  • közepes szint
  • időzítők
Diáklány C# programot készít laptopon

A lecke végére

  • megkülönbözteted a három Timer osztályt (Windows.Forms, Timers, Threading), és tudod, melyik fut a felület szálán;
  • érted, miért nem pontos a Tick (~15 ms felbontás, a felület szál terhelése), és hogy a Tick-számlálás elcsúszik;
  • az időt a Stopwatch vagy az indulás időpontjából számolod, nem a Tick-ekből;
  • visszaszámlálót készítesz szünettel és folytatással;
  • helyesen indítod, leállítod és felszabadítod az időzítőt (Start, Stop, Enabled, Dispose);
  • elkerülöd a tipikus hibákat (hosszú Tick kezelő, párhuzamos Elapsed, felszabadítatlan időzítő).

Mielőtt elkezded

Ehhez a leckéhez az 1.8 lecke (Timer), az 5.1 lecke (események) és a 9.8 lecke (Dispose, using) szükséges.

10.10 Jegyzetkezelő →

Háromféle Timer: melyik, mikor?

OsztályEseményMelyik szálon?Mikor?
System.Windows.Forms.TimerTickA felület szálánFelületet frissítő időzítés (óra, animáció, automatikus mentés). A Toolboxban ez van.
System.Timers.TimerElapsedSzálkészlet (thread pool); a SynchronizingObject beállításával a felület szálánSzerver- és háttérfeladatok; felületi frissítéshez csak SynchronizingObject = this vagy Invoke mellett.
System.Threading.Timervisszahívás (TimerCallback)SzálkészletKönnyű, kód-alapú háttér időzítés; nincs vizuális tervezője.
Form1.csC#
// 1. a Toolbox Timer: a Tick a FELÜLET szálán fut, ezért a vezérlők közvetlenül elérhetők
private void tmrOra_Tick(object sender, EventArgs e)
{
lblIdo.Text = DateTime.Now.ToString("HH:mm:ss"); // rendben
}

// 2. System.Timers.Timer: az Elapsed a szálkészleten fut: a vezérlő elérése hibát okoz!
System.Timers.Timer hatterIdo = new System.Timers.Timer(1000);

hatterIdo.Elapsed += (s, e) =>
{
// lblIdo.Text = ...; // InvalidOperationException: másik szálról nem érhető el a vezérlő (11.3 és 11.7 lecke)
this.BeginInvoke(new Action(() => lblIdo.Text = DateTime.Now.ToString("HH:mm:ss"))); // a felületi szálra küldjük
};

hatterIdo.Start();

// vagy: az Elapsed a felület szálán fusson (a Form a SynchronizingObject)
hatterIdo.SynchronizingObject = this;
Mit választ a kezdő?Felületet frissítő időzítésnél a Windows.Forms.Timer: a Tick a felület szálán fut, nincs szálkezelési gond. A másik kettőt a háttérmunkához használjuk (11.6 lecke).
NévütközésA using System.Windows.Forms; és a using System.Timers; együtt használva a Timer név kétértelmű. Írd ki a teljes nevet (System.Windows.Forms.Timer), vagy ne tedd be a System.Timers névteret.
Hová való a hosszú munka?A Windows.Forms.Timer.Tick a felület szálán fut: ha a kezelő lassú, a felület megfagy (11.3 lecke). Hosszú számítást a Tick-ben ne végezz.
Nem szabadul meg az üzenetsortólA Windows.Forms.Timer a Windows üzenetsorán át kap jelzést: ha a felület szál foglalt, a Tick-ek késnek, és összevonódnak (nem torlódnak fel).

Jósolj!

Egy System.Timers.Timer Elapsed eseménye közvetlenül a lblIdo.Text értéket állítja be. Mi történik?

Jóslat ellenőrzése

InvalidOperationException (Cross-thread operation not valid) (vagy a program csendben hibásan viselkedik, főleg Release módban): az Elapsed a szálkészleten fut, és a vezérlőt csak a létrehozó (felületi) szál érheti el. A megoldás a BeginInvoke, a SynchronizingObject = this, vagy egyszerűen a Windows.Forms.Timer.

Pontosság: miért nem szabad a Tick-eket számolni?

A Windows.Forms.Timer felbontása kb. 15–16 ezredmásodperc, és a Tick késhet, ha a felület szál foglalt. Ha a program minden Tick-nél „eltelt egy másodperc”-et feltételez, az idő elcsúszik. A helyes módszer: indításkor megjegyzed az időt, és minden Tick-nél a tényleges eltelt időt olvasod le a Stopwatch-csal:

Form1.cs (using System.Diagnostics;)C#
// HIBÁS: a Tick-ek számolása; minden késett Tick elvész, az óra lassul
private int masodperc = 0;

private void tmrRossz_Tick(object sender, EventArgs e)
{
masodperc++; // feltételezi, hogy pontosan 1000 ms telt el
lblRossz.Text = TimeSpan.FromSeconds(masodperc).ToString(@"mm\:ss");
}

// HELYES: a tényleges eltelt idő a Stopwatch-ból
private Stopwatch ora = Stopwatch.StartNew();

private void tmrJo_Tick(object sender, EventArgs e)
{
lblJo.Text = ora.Elapsed.ToString(@"mm\:ss\.f"); // akkor is pontos, ha a Tick késik
}

// a Tick gyakorisága és az idő pontossága két külön kérdés:
// a Tick csak a KIJELZÉS frissítésének üteme (200 ms elég), a pontos időt a Stopwatch tartja
tmrJo.Interval = 200;
StopwatchA Stopwatch a rendszer nagy felbontású számlálóját használja (mikroszekundum pontosságú), nem függ a felület szál terhelésétől. Az Elapsed az indítás óta eltelt TimeSpan; a Start, Stop (szünet), Reset, Restart metódusokkal vezérelhető.
Kijelzés és idő szétválasztásaA Tick gyakorisága (Interval) csak azt mondja meg, milyen gyakran frissül a kijelző. Ha 200 ms-onként frissítesz, a másodperc váltása is legfeljebb 200 ms késéssel látszik, de az érték mindig pontos.
DateTime.Now is jóAz indulás időpontját (DateTime.Now) is megjegyezheted, és DateTime.Now - indulas módon számolhatsz. A Stopwatch pontosabb és nem függ a rendszerórától (óraátállítás, téli-nyári időszámítás).
IntervalAz Interval ezredmásodpercben van, értéke legalább 1; a gyakorlati minimum kb. 15 ms. 1 ms-os időzítés a felületen nem megbízható, és fölöslegesen terheli a felület szálat.

Jósolj!

A program tmrRossz (Interval = 1000) mellett a masodperc++ alapján számol, miközben egy másik gomb 5 másodpercig dolgoztatja a felület szálát. Mit mutat az óra 10 másodperc valódi idő után?

Jóslat ellenőrzése

Kb. 5–6 másodpercet: a felület szál foglalt volt, a késett Tick-ek összevonódtak (nem pótlódtak), így a számláló lemaradt. A Stopwatch alapján mért idő 10 másodpercet mutat, függetlenül a Tick-ektől.

Visszaszámláló szünettel

Form1.cs (using System.Diagnostics; using System.Media;)C#
private Stopwatch ora = new Stopwatch();
private TimeSpan osszes = TimeSpan.FromMinutes(5);

private void btnStart_Click(object sender, EventArgs e)
{
if (!ora.IsRunning)
{
if (ora.Elapsed >= osszes) { ora.Reset(); } // lejárt után újraindítás

ora.Start();
tmr.Interval = 200;
tmr.Start();
}
}

private void btnSzunet_Click(object sender, EventArgs e)
{
ora.Stop(); // a Stopwatch megáll, az Elapsed megmarad; a Start folytatja
tmr.Stop();
}

private void btnNulla_Click(object sender, EventArgs e)
{
ora.Reset();
tmr.Stop();
Kijelez();
}

private void tmr_Tick(object sender, EventArgs e)
{
Kijelez();

if (ora.Elapsed >= osszes)
{
ora.Stop();
tmr.Stop();
SystemSounds.Exclamation.Play();
lblAllapot.Text = "Lejárt az idő!"; // hang mellé szöveg is kell: a hang nem mindenkinek hallható (6.9 lecke)
}
}

private void Kijelez()
{
TimeSpan hatra = osszes - ora.Elapsed;

if (hatra < TimeSpan.Zero) { hatra = TimeSpan.Zero; }

// felfelé kerekítve: 4:59,3 → 05:00 helyett 04:59; az utolsó 0,9 mp is „1”-ként látszik
int mp = (int)Math.Ceiling(hatra.TotalSeconds);

lblIdo.Text = (mp / 60).ToString("00") + ":" + (mp % 60).ToString("00");
pb.Value = (int)(ora.Elapsed.TotalSeconds * 100 / osszes.TotalSeconds > 100 ? 100 : ora.Elapsed.TotalSeconds * 100 / osszes.TotalSeconds);
}
Szünet és folytatásA Stopwatch.Stop() megőrzi az eltelt időt, a Start() onnan folytatja. A Reset() nullázza. Ha a Tick számolna, a szünet kezelése sokkal bonyolultabb lenne.
KerekítésA visszaszámlálónál felfelé kerekítünk (Ceiling): a „00:00” csak akkor látszik, amikor tényleg lejárt az idő. Lefelé kerekítve a 00:00 az utolsó másodpercben látszana.
Ne csak hangA lejárat jelzése hang mellett látható szöveggel is történjen (a hangot kikapcsolhatják, vagy a felhasználó nem hallhatja). A villogás veszélyes lehet (6.9 lecke: legfeljebb 3 villanás másodpercenként).
Leállított időzítő és TickEgy már leállított időzítő nem küld új Tick-et, de egy már a sorban álló még megérkezhet. A Tick-ben ezért érdemes ellenőrizni az állapotot (ora.IsRunning).

Jósolj!

A visszaszámláló (int)hatra.TotalSeconds módon (lefelé kerekítve) mutatja a hátralévő időt egy 10 másodperces visszaszámlálón. Mikor jelenik meg először a 00?

Jóslat ellenőrzése

Már az utolsó másodpercben (9 másodperc után): a 0,9 másodperc hátralévő idő lefelé kerekítve 0. A felhasználó azt látja, hogy „00”, pedig még majdnem egy másodperc van hátra. A Math.Ceiling ezt helyesen kezeli.

Az időzítő életciklusa

Form1.csC#
// Start() és Stop() = Enabled = true / false; az Interval módosítása működés közben is érvényes
tmr.Enabled = true;
tmr.Interval = 500;

// egyszeri késleltetés: a Tick-ben az első hívás után leállítjuk magát
private void tmrKesleltet_Tick(object sender, EventArgs e)
{
tmrKesleltet.Stop(); // csak egyszer fut
lblUzenet.Text = ""; // például: az állapotüzenet eltűnik 3 másodperc után
}

// a Designerben létrehozott Timer a komponenseket kezelő gyűjteménybe kerül: a Form zárásakor felszabadul.
// Kódból létrehozott időzítőt a Form zárásakor TE szabadítod fel:
private System.Windows.Forms.Timer villogo = new System.Windows.Forms.Timer { Interval = 500 };

private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
villogo.Stop();
villogo.Dispose();
}
Egyszeri késleltetésA Timer alapból ismétlődik. Ha egyszer akarod, a Tick első sora a Stop(). A felhasználói üzenet (Mentve) eltüntetésére ez gyakori minta.
Designerben felvett TimerA Toolboxból a Formra tett Timer a Form components gyűjteményébe kerül, és a Form Dispose metódusa felszabadítja. Kódból (new Timer) létrehozott példányt neked kell.
Tick bezáráskorEgy futó időzítő a Form bezárása után is küldhet Tick-et (ha nincs leállítva): a kezelő felszabadított vezérlőket érhet el. Zárás előtt a Stop().
EnergiatakarékosságA sokszor ütő időzítő (10 ms) a processzort és az akkumulátort terheli. A kijelzés frissítéséhez a 100–250 ms elég; a nem látható (minimalizált) ablakban az időzítő akár le is állítható.

Jósolj!

A program for ciklusban hívja a tmr.Start() metódust 100-szor. Hány Tick keletkezik intervallumonként?

Jóslat ellenőrzése

Egy: az időzítő egyetlen példány, a többszöri Start() nem hoz létre újabbakat (az Enabled már igaz, a hívás nem hatásos, vagy újraindítja a számlálást). Új időzítőt csak new Timer() hoz létre: ezeket külön fel kell szabadítani.

📷 Képernyőkép helye

Mit tegyél ide: az időmérő-labor: a Tick-számláló és a Stopwatch órája, az elcsúszás, és a visszaszámláló.Javasolt fájlnév: winforms-11-1-idomero.webp · Alt szöveg: Az időmérő-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 óra elcsúszik

masodperc++;   // Tick-enként 1
Hiba és javítás

A késett Tick-ek elvesznek. Használj Stopwatch vagy indulási időpont alapú számítást.

2. Cross-thread kivétel az Elapsed-ben

t.Elapsed += (s, e) => lblIdo.Text = "...";   // System.Timers.Timer
Hiba és javítás

Az Elapsed a szálkészleten fut. Használj Windows.Forms.Timer-t, vagy BeginInvoke / SynchronizingObject.

3. A program lefagy a Tick-ben

private void tmr_Tick(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(3000); // a felület szálát blokkolja
}
Hiba és javítás

A Tick a felület szálán fut: a hosszú munka megfagyasztja az ablakot. Gyors kezelő kell; a hosszú munka a háttérbe (11.5–11.6 lecke).

4. A Tick bezárás után is fut

A Form bezárása után kivétel jelenik meg a Tick kezelőben.

Ok és javítás

Az időzítő nincs leállítva: a FormClosing vagy FormClosed eseményben Stop() (és kódból létrehozottnál Dispose()).

5. Az időzítő nem indul

tmr.Interval = 1000;   // Start() nélkül; az Enabled alapból hamis
Hiba és javítás

A Timer alapból le van állítva. Hívd a Start() metódust, vagy állítsd az Enabled tulajdonságot true értékre (a Designerben is).

6. A lejárat 1 másodperccel korábban látszik

A visszaszámláló az utolsó másodperc előtt 00-t mutat.

Ok és javítás

Lefelé kerekítés ((int)TotalSeconds). Használj Math.Ceiling értéket.

Mini projekt: időmérő-labor

Készíts programot, amely az időzítők pontosságát és használatát mutatja:

  1. Felület: három panel (TabControl fülei): Pontosság, Visszaszámláló, Gyakorlás.
  2. Pontosság fül: két óra egymás mellett: a Tick-számláló (masodperc++) és a Stopwatch óra, mindkettő Interval = 1000; Terhelés (3 mp) gomb, amely a felület szálát 3 másodpercig foglalja (Thread.Sleep), utána az eltérés látszik (lblElteres: A Tick-óra 2,9 másodpercet késik); a Visszaállítás nullázza mindkettőt.
  3. Visszaszámláló fül: a beállítható idő (nudPerc, nudMasodperc), Start, Szünet, Folytat (ugyanaz a Start), Nulla; a ProgressBar a hátralévő arányt mutatja; Ceiling kerekítés; a lejáratkor hang és szöveg (Lejárt az idő!), a villogás legfeljebb 2 Hz.
  4. Gyakorlás fül: az Egyszeri üzenet: a gombra kattintva az állapotsor Mentve üzenete 3 másodperc múlva eltűnik (egyszeri időzítő); a Háttér időzítő (System.Timers) jelölő: az óra az Elapsed-ből frissül BeginInvoke segítségével, majd (kísérletként) közvetlen vezérlő-eléréssel (a kivétel az állapotsorba kerül, nem omlik össze).
  5. Életciklus: minden időzítő leáll a FormClosing-ben; a kódból létrehozott felszabadul.
  6. Terhelés a felületen: az Interval csúszka (TrackBar, 15–1000 ms) a Tick-gyakoriságot állítja; a kijelzés a Stopwatch alapján mindig pontos; a lblTickDb a másodpercenkénti Tick-ek számát mutatja.
  7. Teszteld: a terhelés előtt és után az eltérés, szünet és folytatás a visszaszámlálón, 0 másodperces beállítás, a Form bezárása futó időzítővel, a minimális intervallum, a Cross-thread kísérlet.
Megoldás megnyitása (próbáld ki előbb önállóan!)

A központi rész a Tick-számlálás és a Stopwatch összevetése, és a szünetet kezelő visszaszámláló.

Form1.cs (using System.Diagnostics; using System.Media; using System.Threading;)C#
private int tickMasodperc = 0;
private Stopwatch pontosOra = Stopwatch.StartNew();
private int tickDb = 0;
private DateTime tickMeres = DateTime.Now;

private Stopwatch vissza = new Stopwatch();
private TimeSpan osszes = TimeSpan.FromMinutes(1);

private System.Timers.Timer hatterIdo;

private void Form1_Load(object sender, EventArgs e)
{
tmrPontossag.Interval = 1000;
tmrPontossag.Start();

tmrVissza.Interval = 200;

tbInterval.Minimum = 15;
tbInterval.Maximum = 1000;
tbInterval.Value = 200;
}

private void tmrPontossag_Tick(object sender, EventArgs e)
{
tickMasodperc++;
lblTick.Text = TimeSpan.FromSeconds(tickMasodperc).ToString(@"mm\:ss");
lblPontos.Text = pontosOra.Elapsed.ToString(@"mm\:ss\.f");

double eltérés = pontosOra.Elapsed.TotalSeconds - tickMasodperc;

lblElteres.Text = Math.Abs(eltérés) < 0.5 ? "A két óra egyezik." : "A Tick-óra " + eltérés.ToString("0.0") + " másodpercet késik.";
}

private void btnTerheles_Click(object sender, EventArgs e)
{
Thread.Sleep(3000); // szándékosan: a felület szálát blokkolja (a 11.4 lecke témája)
}

private void btnVisszaallit_Click(object sender, EventArgs e)
{
tickMasodperc = 0;
pontosOra.Restart();
}

// ---- visszaszámláló ----

private void btnStart_Click(object sender, EventArgs e)
{
if (vissza.IsRunning) { return; }

if (vissza.Elapsed == TimeSpan.Zero || vissza.Elapsed >= osszes)
{
osszes = TimeSpan.FromSeconds((int)nudPerc.Value * 60 + (int)nudMasodperc.Value);

if (osszes <= TimeSpan.Zero) { lblAllapot.Text = "Adj meg legalább 1 másodpercet."; return; }

vissza.Reset();
}

vissza.Start();
tmrVissza.Start();
lblAllapot.Text = "Fut...";
}

private void btnSzunet_Click(object sender, EventArgs e)
{
vissza.Stop();
tmrVissza.Stop();
lblAllapot.Text = "Szünet";
}

private void btnNulla_Click(object sender, EventArgs e)
{
vissza.Reset();
tmrVissza.Stop();
Kijelez();
lblAllapot.Text = "";
}

private void tmrVissza_Tick(object sender, EventArgs e)
{
Kijelez();

if (vissza.IsRunning && vissza.Elapsed >= osszes)
{
vissza.Stop();
tmrVissza.Stop();
SystemSounds.Exclamation.Play();
lblAllapot.Text = "Lejárt az idő!"; // hang MELLETT látható szöveg
}
}

private void Kijelez()
{
TimeSpan hatra = osszes - vissza.Elapsed;

if (hatra < TimeSpan.Zero) { hatra = TimeSpan.Zero; }

int mp = (int)Math.Ceiling(hatra.TotalSeconds);

lblIdo.Text = (mp / 60).ToString("00") + ":" + (mp % 60).ToString("00");
pbVissza.Value = osszes.TotalSeconds <= 0 ? 0 : Math.Min(100, (int)(hatra.TotalSeconds * 100 / osszes.TotalSeconds));
}

// ---- egyszeri üzenet ----

private void btnMentve_Click(object sender, EventArgs e)
{
tsslUzenet.Text = "Mentve";
tmrUzenet.Stop();
tmrUzenet.Interval = 3000;
tmrUzenet.Start(); // újraindul, ha már futott
}

private void tmrUzenet_Tick(object sender, EventArgs e)
{
tmrUzenet.Stop(); // egyszeri
tsslUzenet.Text = "";
}

// ---- System.Timers kísérlet ----

private void chkHatter_CheckedChanged(object sender, EventArgs e)
{
if (chkHatter.Checked)
{
hatterIdo = new System.Timers.Timer(1000);

hatterIdo.Elapsed += (s, ev) =>
{
try { this.BeginInvoke(new Action(() => lblHatter.Text = DateTime.Now.ToString("HH:mm:ss"))); }
catch (ObjectDisposedException) { } // a Form közben bezárult
catch (InvalidOperationException) { }
};

hatterIdo.Start();
}
else if (hatterIdo != null)
{
hatterIdo.Stop();
hatterIdo.Dispose();
hatterIdo = null;
}
}

private void btnKiserlet_Click(object sender, EventArgs e)
{
System.Timers.Timer t = new System.Timers.Timer(100) { AutoReset = false };

t.Elapsed += (s, ev) =>
{
try { lblHatter.Text = "közvetlenül"; } // másik szálról: InvalidOperationException
catch (InvalidOperationException ex) { this.BeginInvoke(new Action(() => tsslUzenet.Text = "Cross-thread kivétel: " + ex.Message)); }
finally { t.Dispose(); }
};

t.Start();
}

// ---- a Tick gyakorisága ----

private void tbInterval_Scroll(object sender, EventArgs e)
{
tmrGyakori.Interval = tbInterval.Value;
tmrGyakori.Start();
}

private void tmrGyakori_Tick(object sender, EventArgs e)
{
tickDb++;
lblPontos2.Text = pontosOra.Elapsed.ToString(@"mm\:ss\.fff"); // a kijelzés sűrű, az érték pontos

if ((DateTime.Now - tickMeres).TotalSeconds >= 1)
{
lblTickDb.Text = tickDb + " Tick / mp";
tickDb = 0;
tickMeres = DateTime.Now;
}
}

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
tmrPontossag.Stop();
tmrVissza.Stop();
tmrGyakori.Stop();
tmrUzenet.Stop();

if (hatterIdo != null) { hatterIdo.Stop(); hatterIdo.Dispose(); }
}
📷 Képernyőkép helye

Mit tegyél ide: a kész időmérő-labor három füle.Javasolt fájlnév: winforms-11-1-kesz.webp · Alt szöveg: A kész időmérő-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. Stopper köridőkkel

Készíts stoppert Kör gombbal, amely a köridőket egy listába teszi. Mit jegyzel meg a körökhöz?

Egy lehetséges megoldás

A Stopwatch.Elapsed értékét kör közben, és az előző kör értékét kivonva kapod a köridőt.

Form1.csC#
private TimeSpan utolsoKor = TimeSpan.Zero;private void btnKor_Click(object sender, EventArgs e){    TimeSpan most = ora.Elapsed;    lstKorok.Items.Add("Kör " + (lstKorok.Items.Count + 1) + ": " + (most - utolsoKor).ToString(@"mm\:ss\.ff") + "   (összesen " + most.ToString(@"mm\:ss\.ff") + ")");    utolsoKor = most;}

2. Villogó figyelmeztetés

Egy címke másodpercenként kétszer vált színt (piros/fehér). Mi a felső határ az akadálymentesség szempontjából?

Egy lehetséges megoldás

Legfeljebb 3 villanás másodpercenként (6.9 lecke); a 2 Hz biztonságos. A szöveg tartalma ne csak színnel jelezzen. A felhasználó kikapcsolhatja a villogást.

3. Automatikus kijelentkezés

A program 5 perc tétlenség után zárol. Hogyan méred a tétlenséget?

Egy lehetséges megoldás

Az utolsó felhasználói művelet időpontját (billentyű, egér események) egy mezőben tartod; egy 1 másodperces Timer ellenőrzi: DateTime.Now - utolsoMuvelet > TimeSpan.FromMinutes(5). Nem számolod a Tick-eket.

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 Timer?

Melyiket használod: (a) a felületen frissülő óra; (b) háttérben futó, szálkészletes időzítés?

Megoldás megnyitása

(a) System.Windows.Forms.Timer; (b) System.Timers.Timer vagy System.Threading.Timer.

2. Interval

Mekkora az Interval egysége, és mi a gyakorlati alsó határ?

Megoldás megnyitása

Ezredmásodperc; a felületi időzítő felbontása kb. 15–16 ms.

Közepes szint

3. Pontos idő kijelzése

Miért jó a 200 ms-os Interval egy másodpercet kijelző órához?

Megoldás megnyitása

A kijelzés gyakran frissül (a másodperc váltása nem késik 0,2 másodpercnél többet), a pontos idő a Stopwatch-ból jön, és a Tick ritkán terheli a felületet.

4. Szünet és folytatás Stopwatch nélkül

Hogyan oldanád meg a szünetet, ha csak DateTime.Now alapon dolgoznál?

Megoldás megnyitása

Az eltelt idő összegét (TimeSpan osszeg) és az utolsó indulás időpontját tartod; szünetnél hozzáadod az indulás óta eltelt időt; folytatásnál új időpontot jegyzel. A Stopwatch ezt egyszerűen megteszi.

Kihívás szint

5. Átfedő Elapsed

Egy System.Timers.Timer Elapsed kezelője 3 másodpercig fut, az Interval 1 másodperc. Mi történik?

Megoldás megnyitása

Az Elapsed újra és újra elindul a szálkészleten, párhuzamosan (átfedő futások, versenyhelyzet). Védekezés: AutoReset = false és a végén újraindítás, vagy egy jelző (Interlocked.CompareExchange), amely kihagyja az átfedő hívásokat.

6. Időzítő a háttérszálon

Egy System.Threading.Timer frissíti a felületet. Mi a hiba, és mi a jó megoldás?

Megoldás megnyitása

A visszahívás a szálkészleten fut: a vezérlő közvetlen eléréséhez Invoke / BeginInvoke kell. Egyszerűbb a Windows.Forms.Timer (a Tick a felület szálán fut); a Threading.Timer háttérmunkához való.

Gyors önellenőrzés

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

1. Melyik Timer fut a felület szálán?

A System.Windows.Forms.Timer (a Tick).

2. Miért nem pontos a Tick?

A felbontása kb. 15 ms, és a felület szál terhelése késleltetheti; a késett Tick-ek összevonódnak.

3. Hogyan méred pontosan az időt?

Stopwatch vagy az indulás időpontja alapján, nem a Tick-ek számolásával.

4. Hogyan kezeled a szünetet?

Stopwatch.Stop() és Start() (az Elapsed megmarad).

5. Miért kerekítünk felfelé a visszaszámlálónál?

Hogy a 00:00 csak a lejáratkor látszódjon.

6. Mi az egyszeri időzítő mintája?

A Tick első sora Stop().

7. Mikor kell Dispose-olni az időzítőt?

Kódból létrehozott időzítőt a Form zárásakor (a Designerben felvettet a Form felszabadítja).

Mit tudsz már?

  • Megkülönbözteted a három Timer osztályt, és tudod, melyik fut a felület szálán.
  • Pontos időt mérsz Stopwatch segítségével, a Tick-számlálás helyett.
  • Visszaszámlálót készítesz szünettel, helyes kerekítéssel és akadálymentes jelzéssel.
  • Az időzítő életciklusát (indítás, leállítás, felszabadítás) helyesen kezeled.
  • Elkerülöd a tipikus időzítő-hibákat.

Következő lecke: ProgressBar és állapotjelzés

A folyamatjelző vezérlők és az állapotjelzés tervezése: határozott és határozatlan folyamat, százalék, becsült hátralévő idő, akadálymentes tájékoztatás.