Lekce 3 - Vlákna - Uspání, blokování a stavy vláken v C# .NET
V minulé lekci, Vlákna - Příklady vícevláknových aplikací v C# .NET, jsme si naprogramovali dvě vícevláknové aplikace, ve kterých jsme se naučili vlákna zakládat, pojmenovat a spouštět.
V dnešním C# .NET tutoriálu se naučíme základy
synchronizace vláken. Ukážeme si, jak vlákna
uspat a blokovat. Předs;tavíme si třídu
Thread
a seznámíme se se stavy vláken. Vše si
ukážeme na funkčních příkladech.
Uspaní vláken
V rámci práce s vícevláknovým prostředím nabízí třída
Thread
užitečné metody pro řízení toku vláken, mezi nimiž
patří metoda Sleep()
. Metoda Sleep()
na třídě
Thread
se používá pro dočasné pozastavení
běhu aktuálního vlákna na definovanou dobu v
milisekundách. Typickým použitím je simulace čekání na určitou
událost, nebo zpomalení běhu programu.
Pro zápis doby uspání můžeme v parametru metody
Sleep()
využít strukturu TimeSpan
.
Metoda Sleep()
Uspání vlákna na dobu 1 hodiny provedeme takto:
Thread.Sleep(3600000);
Metoda Sleep()
s
TimeSpan
S využitím struktury TimeSpan
uspíme vlákno na stejnou dobu
takto:
Thread.Sleep(TimeSpan.FromHours(1));
Blokování vláken
Další užitečnou metodou třídy Thread
je metoda
Join()
. Metoda Join()
blokuje volající vlákno,
dokud se vlákno reprezentované instancí typu Thread
neukončí.
Metoda umožňuje synchronizaci vláken a čekání na
dokončení určitého vlákna před pokračováním ve zpracování hlavního
vlákna. To je užitečné v situacích, kdy potřebujeme vyčkat na dokončení
nějaké operace.
Příklad
Nejlépe to uvidíme na příkladu. Založme si novou konzolovou aplikaci.
Budeme pracovat pouze ve třídě Program
.
Proměnná hodnota
Nejdříve si do třídy Program
vložme statickou proměnou
hodnota
, kterou naplňme hodnotou 0
:
static int hodnota = 0;
Metoda PrictiJedna()
Pod metodu Main()
si napišme první metodu, kterou budeme
volat, metodu PrictiJedna()
. V metodě implementujeme zvýšení
hodnoty proměnné hodnota
o hodnotu 1
a její výpis
do konzole:
static void PrictiJedna() { for (int i = 1; i <= 10; i++) { hodnota += 1; } Console.WriteLine("Hodnota proměnné: " + hodnota); }
Metoda OdectiJedna()
Podobně implementujeme metodu OdectiJedna()
. V metodě tedy
snížíme hodnotu proměnné hodnota
o 1
a vypíšeme
ji do konzole:
static void OdectiJedna() { for (int i = 1; i <= 10; i++) { hodnota -= 1; } Console.WriteLine("Hodnota proměnné: " + hodnota); }
Metoda Main()
Pomocné metody implementované máme. Můžeme se tedy vrhnout na
dokončení programu v metodě Main()
:
using System;
using System.Threading;
namespace odpocitavadlo
{
class Program
{
static int hodnota = 0;
static void Main(string[] args)
{
Thread pricteniThread = new Thread(PrictiJedna);
Thread odecitaniThread = new Thread(OdectiJedna);
pricteniThread.Start();
pricteniThread.Join();
odecitaniThread.Start();
odecitaniThread.Join();
Console.WriteLine("Výsledek: " + hodnota);
}
static void PrictiJedna()
{
for (int i = 1; i <= 10; i++)
{
hodnota += 1;
}
Console.WriteLine("Hodnota proměnné: " + hodnota);
}
static void OdectiJedna()
{
for (int i = 1; i <= 10; i++)
{
hodnota -= 1;
}
Console.WriteLine("Hodnota proměnné: " + hodnota);
}
}
}
Vytvoříme dvě vlákna. Vlákno:
pricteniThread
, v jehož konstruktoru zavoláme metoduPrictiJedna()
,odecitaniThread
, v jehož konstruktoru zavoláme metoduOdectiJedna()
.
Obě vlákna spustíme a poté na nich zavoláme metodu Join()
.
Nakonec do konzole vytiskneme výslednou hodnotu proměnné
hodnota
.
Testování
Po spuštění aplikace vidíme, jak se hodnota proměnné
hodnota
mění:
Konzolová aplikace
Hodnota proměnné: 10
Hodnota proměnné: 0
Výsledek: 0
Stavy vláken
Podívejme se na některé ze stavů, které vlákno může nabývat. Stav
vlákna lze zjistit pomocí jeho vlastnosti ThreadState
, která je
výčtovým typem sestávajícím z jedné nebo více z
následujících hodnot:
Running
– Vlákno je v běhu, provádí svůj kód.StopRequested
– Bylo požádáno o zastavení vlákna, ale ještě nebylo zastaveno.SuspendRequested
– Bylo požádáno o pozastavení vlákna, ale ještě nebylo pozastaveno.Background
– Vlákno je nastaveno jako pozadí.Unstarted
– Vlákno bylo vytvořeno, ale ještě nebylo spuštěno.Stopped
– Vlákno bylo zastaveno.WaitSleepJoin
– Vlákno čeká v režimu spánku nebo na spojení.Suspended
– Vlákno bylo pozastaveno.AbortRequested
– Bylo požádáno o zrušení vlákna, ale ještě nebylo zrušeno.Aborted
– Vlákno bylo zrušeno.
Příklad
Ukažme si výpis stavů vláken na příkladu.
Metoda Counting()
V konzolové aplikaci si napíšeme tuto metodu:
static void Counting() { for (int i = 0; i < 5; i++) { Console.WriteLine(i); Thread.Sleep(500); } }
V metodě Counting()
provádíme číslování od 0
do 4
s výpisem čísel. Mezi každý výpis vkládáme pauzu
trvající 500
milisekund.
Metoda Main()
Do metody Main()
přidáme následující kód:
using System;
using System.Threading;
class Program
{
static void Main()
{
Thread thread = new Thread(Counting);
Console.WriteLine("Stav vlákna po vytvoření: " + thread.ThreadState);
thread.Start();
Console.WriteLine("Stav vlákna po spuštění: " + thread.ThreadState);
Thread.Sleep(100);
Console.WriteLine("Stav vlákna po chvilce čekání: " + thread.ThreadState);
thread.Join();
Console.WriteLine("Stav vlákna po dokončení: " + thread.ThreadState);
}
static void Counting()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine(i);
Thread.Sleep(500);
}
}
}
V metodě Main()
vytváříme nové vlákno pomocí třídy
Thread
a jejího konstruktoru, kterému předáváme metodu
Counting()
. Poté vypisujeme stav vlákna pomocí vlastnosti
ThreadState
v různých bodech životního cyklu vlákna.
Stav vlákna je zajímavý pouze ve scénářích ladění. Kód by nikdy neměl používat stav vlákna k synchronizaci aktivit vláken.
Testování
Spuštěním kódu uvidíme výpis stavů vláken:
Konzolová aplikace
Stav vlákna po vytvoření: Unstarted
Stav vlákna po spuštění: Running
0
Stav vlákna po chvilce čekání: WaitSleepJoin
1
2
3
4
Stav vlákna po dokončení: Stopped
V příští lekci, Vlákna - Bezpečnost vláken v C# .NET, se budeme zabývat bezpečností vláken v C# .NET. Vybereme si jednu techniku sdílení dat mezi vlákny, kterou implementujeme do příkladu.