Vlákna v C# - 1. díl
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu

Vlákna v C# - 1. dílVlákna v C# - 1. díl

 

Vlákna v C# - 1. díl

Google       Google       1. 7. 2008       67 227×

Plynule navážeme na minulý díl, ukážeme si, jak předávat data vláknům, zjistíme, že je můžeme pojmenovávat, a naučíme se ošetřovat vzniklé výjimky.

Vytváření a startování vláken

Vlákna jsou vytvářena pomocí konstruktoru třídy Thread předávajícího delegáta ThreadStart – ten označuje metodu, kde by měla začít práce vlákna. Takhle vypadá deklarace delegáta ThreadStart:

public delegate void ThreadStart();

Poté následuje zavolání metody Start() na instanci vlákna, tato akce uvede vlákno do provozu. Funguje až do chvíle, kdy zpracuje všechny příkazy, které jsme mu zadali. Když vše dokončí, Garbage Collector ho odklidí a uvolní paměť.

class Vlakno
{
    static void Main()
    {
        Thread t = new Thread(new ThreadStart(Pis));
        t.Start();    // Spustí Pis() na novém vlákně
        Pis();        // Zároveň s tím zavolá Pis() i na hlavním vlákně
    }

    static void Pis()
    {
        Console.WriteLine("Ahoj!");
    }
}

Tento kód vrátí jako výsledek dvě Ahoj!.

Nebylo by to C#, kdyby se nám celou věc nepokusilo trochu zjednodušit. Můžeme celé ThreadStart vypustit, kompilátor si ho tam umí doplnit sám:

static void Main() 
{
    Thread t = new Thread (Pis);
    ...
}

static void Pis(){ ... }

Další způsob, jak si ušetřit práci, je použití anonymních metod:

static void Main()
{
    Thread t = new Thread(delegate() { Console.WriteLine("Ahoj!"); });
    t.Start();
    Pis();
}

Vlákna mají vlastnost IsAlive, která vrací true, pokud bylo vlákno už spuštěno (tedy byla zavolána metoda Start()), až do jeho zániku. Vlákno po skončení své činnosti nemůže být restartováno, protože ho, jak už jsem zmínil, Garbage Collector odklidí.

Předávání dat delegátovi ThreadStart

Řekněme, že chcete v příkladu nahoře lépe rozlišit, které „Ahoj!“ napsalo které vlákno, třeba tím, že jedno ze slov napíšeme velkými písmeny. Normálně by šlo by předat nějaký parametr metodě Pis(), ale to nemůžeme, protože delegát ThreadStart nepřijímá žádné argumenty. Naštěstí má .NET framework další verzi delegáta a tou je ParametrizedThreadStart, který přímá argument typu object, tak jako v příkladu:

public delegate void ParameterizedThreadStart (object obj);

Upravený příklad z předchozí kapitoly bude vypadat takto:

class ThreadTest
{
    static void Main()
    {
        Thread t = new Thread(Pis);
        t.Start(true);             // == Pis(true) 
        Pis(false);
    }

    static void Pis(object velkaPismena)
    {
        bool velka = (bool)velkaPismena;
        Console.WriteLine(velka ? "AHOJ!" : "ahoj!");
    }
}

Divíte se, kde je nějaký delegát? Kompilátor si ho opět sám dosadí, pokud totiž předáte parametr volané metodě, automaticky se použije ParametrizedThreadStart namísto ThreadStart.

Parametr předávávaný delegátovi ParametrizedThreadStart přijímá právě jeden parametr typu object, při použití ho tedy vždy musíme přetypovat, stejně jako já to udělal s přetypováním na boolean.

Další možností, jak vyřešit příklad nahoře, je opět použití anonymních metod.

static void Main() 
{
    Thread t = new Thread(delegate() { Pis("Ahoj"); });
    t.Start ();
}
    
static void Pis (string text) 
{
    Console.WriteLine(text);
}

Výhoda tohoto postupu spočívá v tom, že metoda WriteLine přijímá libovolný počet argumentů a nejsme omezování jako při použití ParametrizedThreadStart.)

Do třetice, další způsob předávání dat je přes instanční metody namísto statických metod. Jednotlivé vlastnosti instance pak říkají vláknu, co má dělat.

class Vlakna
{
    bool velka;

    static void Main()
    {
        Vlakna instance1 = new Vlakna();
        instance1.velka = true;
        Thread t = new Thread(instance1.Pis);
        t.Start();
        Vlakna instance2 = new Vlakna();
        instance2.Pis();
    }

    void Pis()
    {
        Console.WriteLine(velká ? "AHOJ!" : "ahoj!");
    }
}

Pojmenovávání vláken

Vlákno můžeme pojmenovat přes vlastnost Name. Velmi to usnadňuje debugging (prostě víme, co je které vlákno zač) a s názvy vláken si můžeme hrát i v konzoli.

Jméno vlákna můžeme nastavit kdykoliv se nám zachce, jen musí existovat. Ale pozor, jméno můžeme nastavit jen jednou, jinak dostaneme výjimku!

V následujícím příkladu, protože neběží ve chvíli, kdy upravujeme název vlákna, více než jedno (hlavní) vlákno, můžeme k němu přistoupit přes statickou vlastnost CurrentThread.

class Pojmenovavani
{
    static void Main()
    {
        Thread.CurrentThread.Name = "hlavní";
        Thread pracovni = new Thread(Pis);
        pracovni.Name = "pracovní";
        pracovni.Start();
        Pis();
        Console.ReadKey();
    }

    static void Pis()
    {
        Console.WriteLine("Zdraví vás " + Thread.CurrentThread.Name + " vlákno");
    }
}

Vlákna běžící v popředí a pozadí

Ve výchozím stavu běží vlákna na popředí, to znamená, že aplikace funguje tak dlouho, dokud alespoň jedno z nich běží. C# umožňuje využití i vláken běžících na pozadí – pokud vypneme všechna vlákna na popředí, aplikace se vypne, i když nějaká vlákna v pozadí ještě fungují.

Změna vlákna z popředí na pozadí nezmění jeho prioritu vůči ostatním vláknům, ani potřebný procesorový čas.

Vlákna mají vlastnost IsBackground, která, jak jistě tušíte, nastavuje (pokud má hodnotu true) vlákno na vlákno běžící v pozadí.

class VlaknaNaPozadi
{
    static void Main(string[] args)
    {
        Thread pracovniV = new Thread(delegate() { Console.ReadLine(); });
        if (args.Length > 0) pracovniV.IsBackground = true;
        pracovniV.Start();
    }
}

Pokud je program spuštěn bez parametrů, pracovní vlákno je ve výchozím stavu – běží na popředí, a zastaví se na Console.ReadLine(), kde čeká, až uživatel stiskne klávesu Enter. Mezitím hlavní vlákno pořád běží a aplikace funguje, protože hlavní vlákno je aktivní.

Pokud bychom ale metodě Main() předali nějaký parametr, pracovní vlákno by se přepnulo do práce na pozadí a aplikace by se téměř okamžitě ukončila, protože hlavní (které běží v popředí) hned ukončí svoji práci a nebere ohledy na to, že nějaké vlákno na pozadí ještě běží.

Když je vlákno běžící na pozadí ukončeno takhle „násilně“, přeskočí se v něm i všechny případné bloky „finally“. Toto chování je nežádoucí (proč bychom nějaké finally vůbec psali, kdybychom ho chtěli přeskakovat), a proto je dobré navyknout si počkat vždy než vlákna na pozadí ukončí svou práci a do té doby práci vláken v popředí pozastavit, třeba pomocí Thread.Join (viz minulý díl).

Nastavovat pracovní vlákna jako vlákna běžící na pozadí je výhodné v tom, že máme snadnou kontrolu nad vypínáním aplikace. Představme si opak – vlákno v popředí, které samo při vypnutí aplikace (tedy vypnutí hlavního vlákna) nezemře. Taková aplikace sice zmizí ve Správci úloh ze záložky Aplikace, ale pořád bude její proces aktivní na záložce Procesy. Dokud sám uživatel neukončí na záložce Procesy daný proces, bude běžet a spotřebovávat systémové zdroje.

Nejčastějším zdrojem problémů vypínaných aplikací jsou zapomenutá vlákna běžící na popředí!

Priorita vláken

Vlastnost vláken zvaná Priority určuje, kolik dané vlákno dostane času na vykonání své činnosti. Vzpomínáte na minulý díl, kde jsem se zmiňoval, že CLR přepíná mezi vlákny každou přibližně desetinu milisekundy? Právě vlastností Priority se dá tato hodnota mírně upravit.

Tato vlastnost je udělaná jako výčet (typ enum) hodnot Lowest, BelowNormal, Normal, AboveNormal a High (seřazeno od nejnižší priority po nejvyšší). Nastavená hodnota se projeví jen tehdy, pokud je zároveň spuštěno více vláken.

Ošetřování výjimek

Jakékoliv „obecné“ try/catch/finally bloky nemají žádný význam, pokud je nové vlákno spuštěné, běží totiž na jiné úrovni a bloky, jako v příkladu níže, bude ignorovat.

public static void Main()
{
    try
    {
        new Thread(Pis).Start();
    }
    catch (Exception ex)
    {
        // Sem se ani nikdy nedostaneme!   
        Console.WriteLine("Výjimka!");
    }
}

static void Pis() { throw null; } 

Ke bloku catch ani nedojde, takže ani try by tam nemuselo být. Výsledkem bude nová vlákno s neošetřenou výjimkou NullReferenceException. Řešením je napsání těchto bloků zvlášť pro každou metodu, kterou nové vlákno spouští.

public static void Main() 
{
    new Thread (Pis).Start();
}
 
static void Pis() 
{
    try 
    {
        ...
        throw null;      // tuhle výjimku to už zachytí
        ...
    }
    catch (Exception ex) 
    {
        //Nějaké ošetření výjimky…
        ...
    }
}

Od .NET frameworku 2.0 výše, jakákoliv neošetřená výjimka na vlákně shodí celou aplikaci, takže ignorovat je není způsob jak daný problém vyřešit. Bloky try/catch musí být v každé metodě (abychom 100% zamezili pádům), což při větším počtu metod začíná být skutečně nepraktické. Jste Windows Forms programátor a používáte časté „globální“ zachycování výjimek?

static class Program 
{
    static void Main() 
    {
        Application.ThreadException += Osetreni;
        Application.Run (new MainForm());
    }
 
    static void Osetreni (object sender, ThreadExceptionEventArgs e) 
    {
        // Zachycení, zapsaní, ošetření výjimky…
    }
}

Událost Application.ThreadException se zavolá, když naposled volaný kód (jako odpověď na nějakou Windows zprávu) vytvoří výjimku. Toto řešení sice funguje skvěle, ale dává nám falešný pocit bezpečí. Chyby vytvořené pracovními vlákny totiž ani ThreadException nezachytí. Naštěstí máme k dispozici low-level řešení – AppDomain.UnhandledException. K zavolání dojde kdykoliv, kdy dojde na jakémkoliv vlákně k chybě, v jakémkoliv typu aplikace (ať už s UI nebo bez něj). Ovšem nedoporučuji používat tuto událost jako primární pro zachycení výjimek, použijte ji spíš jako poslední záchranu před pádem aplikace.

A to je konec, příště nás čeká přehled způsobů jak synchronizovat práci vláken.

Zdroj: http://www.albahari.com/threading/#_Creating_Starting

×Odeslání článku na tvůj Kindle

Zadej svůj Kindle e-mail a my ti pošleme článek na tvůj Kindle.
Musíš mít povolený příjem obsahu do svého Kindle z naší e-mailové adresy kindle@programujte.com.

E-mailová adresa (např. novak@kindle.com):

TIP: Pokud chceš dostávat naše články každé ráno do svého Kindle, koukni do sekce Články do Kindle.

Hlasování bylo ukončeno    
0 hlasů
Google
Jakub studuje informatiku na FIT ČVUT, jeho oblíbenou platformou je .NET.
Web     Twitter     Facebook     LinkedIn    

Nové články

Obrázek ke článku Hybridní inteligentní systémy 2

Hybridní inteligentní systémy 2

V technické praxi využíváme často kombinaci různých disciplín umělé inteligence a klasických výpočtů. Takovým systémům říkáme hybridní systémy. V tomto článku se zmíním o určitém typu hybridního systému, který je užitečný ve velmi složitých výrobních procesech.

Obrázek ke článku Jak vést kvalitně tým v IT oboru: Naprogramujte si ty správné manažerské kvality

Jak vést kvalitně tým v IT oboru: Naprogramujte si ty správné manažerské kvality

Vedení týmu v oboru informačních technologií se nijak zvlášť neliší od jiných oborů. Přesto však IT manažeři čelí výzvě v podobě velmi rychlého rozvoje a tím i rostoucími nároky na své lidi. Udržet pozornost, motivaci a efektivitu týmu vyžaduje opravdu pevné manažerské základy a zároveň otevřenost a flexibilitu pro stále nové výzvy.

Obrázek ke článku Síla týmů se na home office může vytrácet. Odborníci radí, jak z pracovních omezení vytěžit maximum

Síla týmů se na home office může vytrácet. Odborníci radí, jak z pracovních omezení vytěžit maximum

Za poslední rok se podoba práce zaměstnanců změnila k nepoznání. Především plošné zavedení home office, které mělo být zpočátku jen dočasným opatřením, je pro mnohé už více než rok každodenní realitou. Co ale dělat, když se při práci z domova ztrácí motivace, zaměstnanci přestávají komunikovat a dříve fungující tým se rozpadá na skupinu solitérů? Odborníci na personalistiku dali dohromady několik rad, jak udržet tým v chodu, i když pracovní podmínky nejsou ideální.

Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032024 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý