NOVINKA! E-learningové kurzy umělé inteligence. Nyní AI za nejlepší ceny. Zjisti více:
NOVINKA – Víkendový online kurz Software tester, který tě posune dál. Zjisti, jak na to!

Lekce 2 - Vlákna - Příklady vícevláknových aplikací v C# .NET

V minulé lekci, Vlákna - Úvod do vícevláknových aplikací v C# .NET, jsme se uvedli do programování vícevláknových aplikací v C# .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 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 TiskJednaANul.

Metoda TiskJedna()

Do třídy Program si napišme metodu pro tisk hodnoty 1 v nekonečném cyklu while:

static void TiskJedna()
{
    while(true)
    {
       Console.Write("1 ");
    }
}

Metoda TiskNula()

Podobně si přidejme metodu TiskNula(), která bude v nekonečném cyklu tisknout hodnotu 0:

static void TiskNula()
{
    while(true)
    {
        Console.Write("0 ");
    }
}

Metoda Main()

Nakonec obě metody zavolejme v metodě Main():

using System;
using System.Threading;

namespace TiskJednaANul
{
    class Program
    {
        static void Main()
        {
            TiskJedna();
            TiskNula();
        }
     ...
    }
}

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ě TiskJedna(). 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:

using System;
using System.Threading;

namespace TiskJednaANul
{
    class Program
    {
        static void Main()
        {
            Thread vlaknoNula = new Thread(TiskNula);
            vlaknoNula.Start();

            TiskJedna();
        }
        ...
    }
}

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 TiskJedna(), 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 ve třídě Program.

Pole assignedTasks

Nejdříve si do třídy Program deklarujeme statické pole assignedTasks:

static string[] assignedTasks = { "Úkol 1 Implementace nového rozhraní", "Úkol 2: Testování aplikace", "Úkol 3: Oprava chyb v kódu" };

Metoda NacitaniUkolu()

V této metodě implementujeme načítání nových úkolů:

static void NacitaniUkolu()
{
    string threadName = Thread.CurrentThread.Name;
    Console.WriteLine($"[{threadName}] Načítání nových úkolů...");
    Thread.Sleep(2000);
    Console.WriteLine($"[{threadName}] Nové úkoly načteny.");

    foreach (var task in assignedTasks)
    {
        Console.WriteLine($"[{threadName}] Přiřazování úkolu: {task}");
        Thread.Sleep(1000);
    }

    Console.WriteLine($"[{threadName}] Všechny úkoly přiřazeny");
}

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 ZpracovaniUkolu()

V této metodě budeme načtené úkoly zpracovávat:

static void ZpracovaniUkolu()
{
    string threadName = Thread.CurrentThread.Name;
    Console.WriteLine($"[{threadName}] Zpracovávání úkolů:");

    for (int i = 1; i <= assignedTasks.Length; i++)
    {
        Console.WriteLine($"[{threadName}] Zpracovávání úkolu {i}");
        Thread.Sleep(2000);
    }

    Console.WriteLine($"[{threadName}] Všechny úkoly zpracovány");
}

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:

        static void Main()
        {
            Thread nacitaciVlakno = new Thread(NacitaniUkolu);
            nacitaciVlakno.Name = "Načítací vlákno pro úkoly";
            nacitaciVlakno.Start();

            Thread zpracovavaciVlakno = new Thread(ZpracovaniUkolu);
            zpracovavaciVlakno.Name = "Zpracovávací vlákno pro úkoly";
            zpracovavaciVlakno.Start();
        }

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 v C# .NET , se naučíme základy synchronizace vláken v C# .NET. Ukážeme si, jak vlákna uspat i blokovat, a seznámíme se se stavy vláken.


 

Předchozí článek
Vlákna - Úvod do vícevláknových aplikací v C# .NET
Všechny články v sekci
Paralelní programování a vícevláknové aplikace v C# .NET
Přeskočit článek
(nedoporučujeme)
Vlákna - Uspání, blokování a stavy vláken v C# .NET
Článek pro vás napsal Filip Studený
Avatar
Uživatelské hodnocení:
22 hlasů
.
Aktivity