Skip Navigation Links.
Skip Navigation Links.

.net student

microsoft

Vyhledávání v obsahu portálu


Přihlášení uživatele

Přihlášení uživatele

Členové:
  • Poslední nový uživatel Poslední: felhell
  • Nový dnes Dnes nových 2
  • Nový včera Včera nových: 6
  • Počet uživatelů: Celkem: 4412
Lidé online:
  • Návštěvníci: Návštěvníků: 28
  • Registrovaných Členů: 0
  • Celkem Celkem: 28
Mapa Stranek

MCTS 6. díl - Vlákna

Vlákna nám umožnují vytvářet vysoce výkonné aplikaci, ale nesmíme to přehánět, protože příliš mnoho vláken škodí. Pro práci s vlákny nabízí .NET Framework namespace System.Threading, ve kterém se nachází potřebné třídy pro vytvoření a správu vláken. Jednotné vlákno představuje třída Thread. Podívejme se tedy na základní vlastnosti a metody třídy Thread.

Thread

properties

  • bool IsAlive - vrací hodnotu signalizující zda-li vlákno žije
  • bool isBackground - vrací hodnotu signalizující zda.li běží vlákno na pozadí
  • int ManagedThreadId - vrací id aktuálního vlákno
  • string Name - vrací či nastavuje název vlákna
  • ThreadPriority Priority - vrací či nastavuje prioritu vlákna. Možné priority:
    • AboveNormal, BelowNormal, Highest, Lowest, Normal
  • ThreadState ThreadState - vrací stav vlákna. Možné stavy:
    • Aborted, AbortRequested, Background, Running, Stopped, StopRequested, Suspended, SuspendRequest, Unstarted, WaitSleepJoin

methods

  • void Start - spustí vlákno
  • void Join - blokuje ostatní vlákna dokud neskončí aktuální vlákno
  • void Abort - vyvolá výjimku ThreadAbortException signalizující, že by vlákno mělo být ukončeno
  • void Interrupt - vyvolá výjimku ThreadInterruptedException jestliže je vlákno v blokovací stavu (ThreadState.WaitJoinSleep). Jestliže vlákno není blokováno, přerušení nikdy nenastane
  • void Resume, Suspend - zastaralé, nedoporučuje se používat

Po trošcích teorie se pojdmě podívat na kousek kódu reprezentující spuštění kódu v novém vláknu. Třída Thread přijímá jako parametr objekt typu ThreadStart nebo ParameterizedThreadStart, který slouží k předání parametru novému vláknu, ale o tom později.

static void Main(string[] args)
{
    // vytvorim objekt typu ThreadStart
    ThreadStart vstupVlakna = new ThreadStart(NejakaMetoda);
    // vytvorim nove vlakno
    Thread vlakno = new Thread(vstupVlakna);
    // spustime vlakno
    vlakno.Start();
}

public static void NejakaMetoda()
{
    Console.WriteLine("Metoda spustena v novem vlakne");
}

 

Při spuštění více vláken najednou je jejich použití zajímavější. Ukážeme si to na jednoduchém příkladě, kdy si spustíme několik vláken vykonávající ovšem stejný kód:

static void Main(string[] args)
{
    // vytvorim objekt typu ThreadStart
    ThreadStart vstupVlakna = new ThreadStart(Citac);
    // vytvorim nova vlakno
    Thread[] vlakna = new Thread[10];
    for (int i = 0; i < vlakna.Length; i++)
    {
        // vytvorim nove vlakno
        vlakna[i] = new Thread(vstupVlakna);
        // a spustim
        vlakna[i].Start();
    }
}

public static void Citac()
{
    Console.WriteLine("ID vlakna: {0}", Thread.CurrentThread.ManagedThreadId);
}

Pořadí vláken není přesně dáno a proto v případě, že chceme postupně provést několik vláken v konkrétním pořadí za sebou můžeme využít metodu Join, která nám zaručí, že se další vlákno spustí až skončí předchozí. V našem ukázkovém příkladu to je zbytečné, ale kdyby každé vlákno provádělo různě časově náročné operace, kód by vypadal takto

// projdeme kolekci vlaken
foreach (Thread t in vlakna)
{
    // a zavolame prislusnou metodu
    t.Join();
}

 Nyní se vrátíme k objektu typu ParameterizedThreadStart o kterém jsem se zmínil výše. Vytvoření vlákna a jeho spuštění s parametrem by vypadalo následovně. Nesmíme ovšem zapomenout, že metoda, kterou definujeme při vytváření ParameterizedThreadStart objektu musí přijímat jako parametr objekt typu Object!

static void Main(string[] args)
{
    // vytvorim objekt typu ParameterizedThreadStart
    ParameterizedThreadStart paramVstupVlakna = new ParameterizedThreadStart(Citac);
    // vytvorim nove vlakno
    Thread vlakno = new Thread(paramVstupVlakna);
    // spustim vlakno s parametrem
    vlakno.Start("Muj parametr");
}

public static void Citac(object vstup)
{
    Console.WriteLine("ID vlakna: {0}, parametr: {1}",
        Thread.CurrentThread.ManagedThreadId, vstup);
}

Sdílení dat mezi více vlákny

Nejdříve si ukážeme kód a poté se podíváme na chyby a jejich řešení: 

public static int cislo;

static void Main(string[] args)
{
    ThreadStart start = new ThreadStart(Inkrementuj);

    Thread[] vlakna = new Thread[10];
    // spustim 10 vlaken, ktere budou provadet kod
    // v metode Inkrementuj
    for (int i = 0; i < 10; i++)
    {
        vlakna[i] = new Thread(start);
        vlakna[i].Start();
    }
    // pockam az vsechny vlakna skonci
    foreach (Thread t in vlakna)
    {
        t.Join();
    }
    // zobrazim vysledek
    Console.WriteLine("Celkovy pocet: {0}", Program.cislo);
    Console.ReadLine();
}

public static void Inkrementuj()
{
    for (int i = 1; i <= 10000; i++)
    {
        Program.cislo = Program.cislo + 1;
    }
}
Máme 10 vláken, které vykonávají stejnou operaci, tzn. , že každé vlákno provádí kód v metodě inkrementuj. Hodnota čísla se zvětší o 10 000, celkeme tedy 10 x 10 000 = 100 000. Pokud tento kód spustíme na jednojádrovém procesoru, tak výsledek by opravdu měl být 100 000, ovšem spustíme-li kód na vícejádrovém procesoru, dostaneme výsledek menší (ne vždy) než 100 000 ! Proč?

Inkrementace proměnné cislo se na jednojádrovém procesoru provádí ve třech krocích

  1. načtení hodnoty z paměti
  2. inkrementace hodnoty
  3. zapsání hodnoty zpět do paměti

u dvoujádrového

  1. první proces přečte hodnotu z paměti
  2. a inkrementuje

předtím, než ovšem dojde k zápisu zpět do paměti, je proces pozastaven a druhý proces spuštěn

  1. druhý proces přečte hodnotu z paměti
  2. inkrementuje
  3. a zapíše novou hodnotu zpět do paměti

druhý proces je pozastaven a první opět povolen

  1. první proces zapíše nyní špatnou hodnotu zpět do paměti

Jelikož náš kód není nijak ošetřen, tak během těchto operací dochází ke ztrátám některých hodnot, proto naše cislo není vždy 100 000. Jak to tedy ošetříme?

Využijeme třídu Interlocked, která nabízí statickou metodu Increment pro inkrementování proměnné. V metodě Inkrementuj uděláme menší změny

//Program.cislo = Program.cislo + 1;
Interlocked.Increment(ref Program.cislo);

 

tím zaručíme, že inkrementace bude správná a číslo tedy nebude menší než 100 000. Další možností je využít tzv. zámek (Lock), kterým si danou část uzamkneme a nikdo nám tam nevstoupí

public int cislo;

static void Main(string[] args)
{
    Program p = new Program();
    ThreadStart start = new ThreadStart(p.Inkrementuj);

    Thread[] vlakna = new Thread[10];
    // spustim 10 vlaken, ktere budou provadet kod
    // v metode Inkrementuj
    for (int i = 0; i < 10; i++)
    {
        vlakna[i] = new Thread(start);
        vlakna[i].Start();
    }
    // pockam az vsechny vlakna skonci
    foreach (Thread t in vlakna)
    {
        t.Join();
    }
    // zobrazim vysledek
    Console.WriteLine("Celkovy pocet: {0}", p.cislo);
    Console.ReadLine();
}
public void Inkrementuj()
{
    lock (this)
    {
        for (int i = 1; i <= 10000; i++)
        {
            cislo = cislo + 1;
        }
    }
}
nebo případně využít třídu Monitor 
public void Inkrementuj()
{
    // vytvorime zamek
    Monitor.Enter(this);
    try 
    {            
         for (int i = 1; i <= 10000; i++)
        {
            cislo = cislo + 1;
        }
    }
    finally
    {
        // uvolnime zamek
        Monitor.Exit(this);
    }
}

Při používání zámků si musíme dát pozor na tzv. Deadlocks

class Deadlocker
{
    object ResourceA = new object();
    object ResourceB = new object();

    public void First()
    {
        lock (ResourceA)
        {
            lock (ResourceB)
            {
                // nejaky kod
            }
        }
    }
    public void Second()
    {
        lock (ResourceB)
        {
            lock (ResourceA)
            {
                // nejaky kod
            }
        }
    }
}

 V metodě main vytvoříme jedno vlákno, které bude provádět metodu First a druhé vlákno provádějící metodu second a spustíme

Deadlocker deadlock = new Deadlocker();

ThreadStart firstStart = new ThreadStart(deadlock.First);
ThreadStart secondStart = new ThreadStart(deadlock.Second);

Thread first = new Thread(firstStart);
Thread second = new Thread(secondStart);

first.Start();
second.Start();

first.Join();
second.Join();
dostaneme se do situace, že

  1. první vlákno uzamkne ResourceA
  2. druhé vlákno uzamkne ResourceB
  3. první vlákno čeká na uvolnění zámku ResourceB
  4. druhé vlákno čeká na uvolnění zámku resourceA

tyto 4 kroky nám signalizují "deadlock".

Mezi další synchronizační metody patří využití

  • ReaderWriterLock třídy
  • synchronizace pomocí windows objektů
    • Mutex
    • Semaphore
    • AutoresetEvent
    • ManualResetEvent

Asynchronní programování

Opět si představme, že v našem programu načítáme data ze souboru, ale jejich načítání trvá docelá dlouho. Proto abychom se zbavili odporných "hodin", můžeme načítání ze souboru provádět asynchronně a tudíž naše aplikace "nezatuhne". Ukážeme si to na příkladě

    
using (FileStream fs = new FileStream("S:\\test.txt", FileMode.Open,
                                          FileAccess.Read, FileShare.Read,
                                          1024, true))
    {
        byte[] buffer = new byte[fs.Length];

        IAsyncResult result = fs.BeginRead(buffer, 0, buffer.Length, null, null);

        while (!result.IsCompleted)
        {
            Console.WriteLine("ctu...");
        }
        int bytes = fs.EndRead(result);

        Console.WriteLine("Precteno {0} byte-u", bytes);
        Console.ReadLine();
    }

Metodou BeginRead začneme číst data asynchronně. Pokud víme že čtení bude trvat déle, můžeme v cyklu while provádět další operace. A metoda EndRead nám vrátí počet přečtených byte-ů. Je možné si také vytvořit delegáta, která bude vykonán, až asynchronní čtení skončí

 

static void Main(string[] args)
{
    FileStream fs = new FileStream("S:\\test.txt", FileMode.Open,
                                   FileAccess.Read, FileShare.Read,
                                   1024, true);

    byte[] buffer = new byte[fs.Length];

    IAsyncResult result = fs.BeginRead(buffer, 0, buffer.Length, 
                          new AsyncCallback(CteniDokonceno), fs);
    Console.ReadLine();
    
}

public static void CteniDokonceno(IAsyncResult result)
{
    Console.WriteLine("Cteni dokonceno");
    using (FileStream fs = (result.AsyncState as FileStream))
    {
        int bytu = fs.EndRead(result);
        Console.WriteLine("Precteno {0} byte-u", bytu);
        Console.ReadLine();
    }
}
Nyní jsme metodě BeginRead předali jako parametr delegáta a filestream. Jakmile bude čtení dokončeno, provede se metoda CteniDokonceno

To je z dnešního krátkého souhrnu o vláknech vše. Naviděnou u dalšího dílu, který bude věnovaný nástrojům pro logování, debuggování, trasování a práci s WMI.

Lukáš

Lukáš Kubis :: 28. září 2008 :: 333 shlédnutí :: 0 komentářů
kategorie: Obecná .Net témata, Vývoj Windows aplikací

Comments

Nyní zde nejsou žádné kometáře. Buďte první!
Musíte být přihlášen pro posílání komentářů. Přihlásit se můžete zde
Přehled posledních diskuzí

Přehled posledních diskuzí

  1. Nekultura výlepu plakátů [11.21.2008 8:00 dop.]
    Proč proboha je na FEL ČVUT na Karláku budova polepena plakáty na zdech, přímo na omítce? Vrcholnou ukázkou je zeď před ...
  2. RE: SQL server 2008 MSDN AA [11.11.2008 7:26 odp.]
    Diky, ale tim to bohuzel neni.
  3. Dotaz na organizátory MS Festu [11.10.2008 4:31 odp.]
    Ahoj MSP,rád bych se zeptal jednoho z organizátorů MS Festu na jeden nestandardní dotaz, který se mi nechce psát na veře...
  4. RE: SQL server 2008 MSDN AA [11.08.2008 1:51 odp.]
    Dobrý den,pro spuštění instalace SQL Serveru 2008 je třeba mít nainstalován .NET Framework 3.5 Service Pack 1 - je to uv...
Novinky z klubů

Novinky z klubů

  1. Programátorské večery: Tipy a triky pro Visual Studio 13. listopadu 2008
    S Visual Studiem pracujeme každý den, ale málokdo zná šechny užitečné klávesov...
  2. Programátorské večery: PowerShell 12. listopadu 2008
    Platformu PowerShell již netřeba představovat. Co ovšem přináší pro .NET programátory...
  3. Programátorské večery: Entity Framework 30. října 2008
    Na této akci bude představena technologie Entity Framework. Podíváme se na tato témata: ...
  4. Programátorské večery: LINQ 26. října 2008
    LINQ je nový revoluční přístup pro práci s daty, který přichází s .NET ...
Co se píše jinde

Co se píše jinde

Řešení problémů s identitou webu u Personal Information Cards

Téměř přesně před rokem jsem si v článku Jak se zjišťuje identita webu u Personal Information Cards? stěžoval na to, ...

TechEd Developers 2008: Den druhý až čtvrtý

Konečně jsem získal dostatek času, abych sepsal zážitky z druhého až čtvrtého dne TechEdu.

TechEd Developers 2008: Den první

První den TechEdu je vždycky takový nanečisto, ostatně jsou v něm jenom dva přednáškové sloty. A keynote. Na co že se...

Výběr serveru

Výběr vhodného serveru představuje volbu při níž musíme přihlédnout k mnoha kritériím, které lze shrnout do následují...

Podívejte se své cache na zoubek

Robustní cacheovací mechanismus je jedna z nejužitečnějších technologií, jaké ASP.NET nabízí. Při vhodném použití můž...