Lekce 2 - Vlákna - Příklady vícevláknových aplikací ve VB.NET
V minulé lekci, Vlákna - Úvod do vícevláknových aplikací ve VB.NET, jsme se uvedli do programování
vícevláknových aplikací ve VB.NET. Vysvětlili jsme si pojmy jako proces,
vlákna a jejich synchronizace. Pracovali jsme s vláknem na třídě
Thread
z .NET.
V dnešním VB.NET tutoriálu si vyzkoušíme naprogramovat dvě vícevláknové aplikace, ve kterých se naučíme vlákna zakládat, pojmenovat a spouštět.
Tisk jedniček a nul
V naší první vícevláknové aplikaci si ukážeme paralelní běh
dvou vláken na střídavém tisku hodnot 1
a
0
do konzole:
Konzolová aplikace
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ...
Založme si novou konzolovou aplikaci s názvem
TiskJednicek_A_Nul
.
Metoda TiskniJednicky()
Do třídy Program
si napišme metodu pro tisk hodnoty
1
v nekonečném cyklu while
:
Sub TiskniJednicky() While True Console.Write("1 ") End While End Sub
Metoda TiskNula()
Podobně si přidejme metodu TiskNula()
, která bude v
nekonečném cyklu tisknout hodnotu 0
:
Sub TiskNula() While True Console.Write("0 ") End While End Sub
Metoda Main()
Nakonec obě metody zavolejme v metodě Main()
:
Module Module1 Sub Main() TiskniJednicky() TiskNula() End Sub ... End Module
Testování
Když aplikaci spustíme, vidíme neustálý tisk čísla 1
:
Konzolová aplikace
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ...
Na tisk čísla 0
se samozřejmě nedostalo, protože jsme
zůstali zacykleni v metodě TiskniJednicky()
. Pojďme to napravit
přidáním dalšího vlákna.
Úprava metody Main()
V metodě Main()
vytvoříme nové vlákno pro tisk nuly pomocí
třídy Thread
:
Imports System.Threading Module Module1 Sub Main() Dim vlaknoNula As New Thread(AddressOf TiskNula) vlaknoNula.Start() TiskniJednicky() End Sub ... End Module
Nejdříve vytvoříme nové vlákno vlaknoNula
, v jehož
konstruktoru zavoláme naši metodu TiskNula()
. Vlákno
vlaknoNula
poté spustíme. Nakonec zavoláme metodu
TiskniJednicky()
, která bude v nekonečném cyklu tisknout hodnotu
1
do konzole.
Testování
Po spuštění aplikace nyní v konzoli vidíme střídání tisku čísel
1
a 0
, což dokazuje paralelní běh obou vláken:
Konzolová aplikace
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ...
Pořadí tisku 1
a 0
se bude lišit
při každém spuštění aplikace.
V ukázce vidíme, jak operační systém poskytuje plánovač vláken. Plánovač vláken rozhoduje o tom, které vlákno má právo vykonat svůj kód. Plánovač může přidělovat časové kvantum každému vláknu. Když jedno vlákno dosáhne konce svého časového kvanta, plánovač přepne na jiné vlákno.
Úkoly
Představme si aplikaci, která zpracovává úkoly v aplikaci. Založme si
konzolovou aplikaci s názvem Ukoly
. V naší aplikaci bude:
- První vlákno odpovědné za načtení úkolů ("Načítací vlákno").
- Druhé vlákno bude provádět načtené úkoly ("Zpracovávací vlákno").
Výstup z aplikace bude vypadat takto:
Konzolová aplikace
[Načítací vlákno pro úkoly] Načítání nových úkolů...
[Zpracovávací vlákno pro úkoly] Zpracovávání úkolů...
[Načítací vlákno pro úkoly] Nové úkoly načteny.
[Načítací vlákno pro úkoly] Přiřazování úkolu: Úkol 1: Implementace nového rozhraní.
[Zpracovávací vlákno pro úkoly] Zpracovávání úkolu 1.
[Načítací vlákno pro úkoly] Přiřazování úkolu: Úkol 2: Testování aplikace.
[Načítací vlákno pro úkoly] Přiřazování úkolu: Úkol 3: Oprava chyb v kódu.
[Načítací vlákno pro úkoly] Všechny úkoly přiřazeny.
[Zpracovávací vlákno pro úkoly] Zpracovávání úkolu 2.
[Zpracovávací vlákno pro úkoly] Zpracovávání úkolu 3.
[Zpracovávací vlákno pro úkoly] Všechny úkoly zpracovány.
Budeme pracovat pouze v modulu Module1.vb
.
Pole assignedTasks
Nad metodu Main()
deklarujeme pole
assignedTasks()
:
Dim assignedTasks() = {"Úkol 1: Implementace nového rozhraní", "Úkol 2: Testování aplikace", "Úkol 3: Oprava chyb v kódu"}
Metoda NactiUkoly()
V této metodě implementujeme načítání nových úkolů:
Sub NactiUkoly() Dim threadName As String = Thread.CurrentThread.Name Console.WriteLine($"[{threadName}] Načítání nových úkolů...") Thread.Sleep(2000) Console.WriteLine($"[{threadName}] Nové úkoly načteny.") For Each task As String In assignedTasks Console.WriteLine($"[{threadName}] Přiřazování úkolu: {task}") Thread.Sleep(1000) Next Console.WriteLine($"[{threadName}] Všechny úkoly přiřazeny") End Sub
Nejprve si uložíme název vlákna do proměnné threadName
pomocí vlastnosti Thread.CurrentThread.Name
. Poté vytiskneme
název vlákna do konzole spolu s informacemi o průběhu načítání a
přiřazování úkolů.
Metoda ZpracujUkoly()
V této metodě budeme načtené úkoly zpracovávat:
Sub ZpracujUkoly() Dim threadName As String = Thread.CurrentThread.Name Console.WriteLine($"[{threadName}] Zpracovávání úkolů:") For i As Integer = 1 To assignedTasks.Length Console.WriteLine($"[{threadName}] Zpracovávání úkolu {i}") Thread.Sleep(2000) Next Console.WriteLine($"[{threadName}] Všechny úkoly zpracovány") End Sub
Nejprve si uložíme název vlákna do proměnné threadName
pomocí vlastnosti Thread.CurrentThread.Name
. Poté vytiskneme
název vlákna do konzole spolu s informacemi o průběhu zpracování
úkolů.
Metoda Main()
A nakonec v metodě Main()
obě vlákna spustíme:
Imports System.Threading
Module Module1
Dim assignedTasks() = {"Úkol 1 Implementace nového rozhraní", "Úkol 2: Testování aplikace", "Úkol 3: Oprava chyb v kódu"}
Sub Main()
Dim nacitaciVlakno As New Thread(AddressOf NactiUkoly)
nacitaciVlakno.Name = "Načítací vlákno pro úkoly"
nacitaciVlakno.Start()
Dim zpracovavaciVlakno As New Thread(AddressOf ZpracujUkoly)
zpracovavaciVlakno.Name = "Zpracovávací vlákno pro úkoly"
zpracovavaciVlakno.Start()
Console.ReadKey()
End Sub
Sub NactiUkoly()
Dim threadName As String = Thread.CurrentThread.Name
Console.WriteLine($"[{threadName}] Načítání nových úkolů...")
Thread.Sleep(2000)
Console.WriteLine($"[{threadName}] Nové úkoly načteny.")
For Each task As String In assignedTasks
Console.WriteLine($"[{threadName}] Přiřazování úkolu: {task}")
Thread.Sleep(1000)
Next
Console.WriteLine($"[{threadName}] Všechny úkoly přiřazeny")
End Sub
Sub ZpracujUkoly()
Dim threadName As String = Thread.CurrentThread.Name
Console.WriteLine($"[{threadName}] Zpracovávání úkolů:")
For i As Integer = 1 To assignedTasks.Length
Console.WriteLine($"[{threadName}] Zpracovávání úkolu {i}")
Thread.Sleep(2000)
Next
Console.WriteLine($"[{threadName}] Všechny úkoly zpracovány")
End Sub
End Module
V metodě vytvoříme vlákna nacitaciVlakno
a
zpracovavaciVlakno
. Do vlastnosti Name
vložíme
jejich názvy. A pak už jen metodou Start()
obě vlákna
spustíme.
Testování
Po spuštění aplikace uvidíme výpis práce obou vláken:
Konzolová aplikace
[Načítací vlákno pro úkoly] Načítání nových úkolů...
[Zpracovávací vlákno pro úkoly] Zpracovávání úkolů
[Načítací vlákno pro úkoly] Nové úkoly načteny
[Načítací vlákno pro úkoly] Přiřazování úkolu: Úkol 1: Implementace nového rozhraní
[Zpracovávací vlákno pro úkoly] Zpracovávání úkolu 1
[Načítací vlákno pro úkoly] Přiřazování úkolu: Úkol 2: Testování aplikace
[Načítací vlákno pro úkoly] Přiřazování úkolu: Úkol 3: Oprava chyb v kódu
[Načítací vlákno pro úkoly] Všechny úkoly přiřazeny
[Zpracovávací vlákno pro úkoly] Zpracovávání úkolu 2
[Zpracovávací vlákno pro úkoly] Zpracovávání úkolu 3
[Zpracovávací vlákno pro úkoly] Všechny úkoly zpracovány
V příští lekci, Vlákna - Uspání, blokování a stavy vláken ve VB.NET, se naučíme základy synchronizace vláken ve VB.NET. Ukážeme si, jak vlákna uspat i blokovat, a seznámíme se se stavy vláken.