7. Vlákna

Aplikace může běžet v jednom nebo více vláknech (tocích), která se vykonávají "paralelně" v téže aplikaci.

7.1. Jednoduchá vícevláknová aplikace

[ukázka kódu]
using System;
using System.Threading;

class ThreadTest
{
  static void Main(){
    Thread t = new Thread(new ThreadStart(Run));
    t.Start();
    Run();
   }
  static void Run()
  {
    for(char c='a'; c<'z'; c++)
      Console.Write(c);
  }
}

V příkladu se zkonstruuje nový objekt vlákna předáním delegáta ThreadStart, který obaluje metodu, specifikující, kde má začít vykonávání daného vlákna. Pak se vlákno spustí a zavolá metodu Run(). Obě vlákna začnou vypisovat abecedu do konzole. Nevýhodou je, že sdílejí konzoli společně, takže se může (a pravděpodobně i stane), že budou obě abecedy zapletené do sebe.

7.2. Synchronizace vláken

Technika zajišťující koordinovaný přístup ke sdíleným prostředkům.

7.2.1. Příkaz lock

Příkaz lock zajišťuje, že k nějakému bloku kódu může přistoupit pouze jedno vlákno.

[ukázka kódu]
using System;
using System.Threading;

class LockTest
{
  static void Main()
  {
    LockTest lt = new LockTest();
    Thread t = new Thread(new ThreadStart(lt.Run));
    t.Start();
    lt.Run();
  }
  public void Run()
  {
    lock(this) {
      for(char c='a'; c<'z'; c++)
        Console.Write(c);
    }
  }
}

Výsledkem bude v konzoli dvakrát za sebou napsaná abeceda.

7.2.2. Operace Pulse a Wait

Operace umožňující vláknům navzájem komunikovat prostřednictvím monitoru, který udržuje seznam vláken čekajících na obdržení zámku nějakého objektu.

[ukázka kódu]
using System;
using System.Threading;

class MonitorTest
{
  static void Main()
  {
    MonitorTest mt = new MonitorTest();
    Thread t = new Thread(new ThreadStart(mt.Run));
    t.Start();
    mt.Run();
  }
  static void Run(){
    for(char c='a'; c<'z'; c++)
    lock(this)
    {
      Console.Write(c);
      Monitor.Pulse(this);
      Monitor.Wait(this);
    }
  }
}

Metoda Pulse říká monitoru, aby vzbudil další vlákno, které čeká na zámek daného objektu. Volání metody Wait vlákno uspí. Výsledkem programu bude v konzoli napsaná zdvojená abeceda aabbccdd....

7.2.3. Atomické operace

Atomická operace je operace, která nemůže být přerušena jiným vláknem a tudíž není třeba používat zámku. Například aktualizování proměnné je atomická operace, protože je zaručeno dokončení operace bez předání řízení jinému vláknu.

7.3. Obvyklé typy vláken

7.3.1. Třída Monitor

System.Threading.Monitor poskytuje implementaci Hoareho monitoru. Jde o nejzákladnější třídu vláken.

7.3.2. Metody Enter a Exit

Získávají (resp. uvolňují) zámek nějakého objektu. Pokud nějaký objekt drží nějaké vlákno, pak metoda Enter() čeká, až bude zámek uvolněn nebo dokud není vlákno přerušeno výjimkou ThreadInterruptedException. Každému volání Enter() by mělo odpovídat volání Exit() pro tentýž objekt v tomtéž vlákně.

7.3.3. Metoda PulseAll

Pokud čeká ve frontě monitoru na zámek více vláken, tak metoda Pulse() probudí jen to první ve frontě. Metoda PulseAll() postupně probudí všechny.

7.4. Asynchronní delegáti

Je možné zavolat nějakou metodu asynchronně. Runtime nabízí standardní způsob asynchronního volání metod.

7.4.1. Invoke

návratový-typ Invoke(seznam-parametrů);

Invoke() volá metodu asynchronně a volající musí čekat až delegát skončí vykonávání (standardní volání delegáta v C# volá Invoke())

7.4.2. BeginInvoke a EndInvoke

IAsyncResult BeginInvoke(seznam-parametrů, IAsyncCallback ac, object stav);

návratový-typ EndInvoke(ref/out seznam-parametrů, IAsyncCallback ac);

BeginInvoke volá delegáta se zadaným seznamem parametrů a pak se okamžitě vrací. Toto volání se provede, je-li v ThreadPool k dispozici volné vlákno. EndInvoke přebírá návratovou hodnotu volané metody se všemi odkazovanými parametry a výstupními parametry. Mohlo totiž dojít k jejich změně.

[ukázka kódu]
using System;
using System.Threading;
using System.Runtime.Remoting.Messaging;

delegate int Compute(string s);

class Class1
{ 

  static int TimeConsumingFunction(string s)
  {
    return s.Length;
  }
  static void ViewResultFunction(IAsyncResult ar)
  {
    Compute c = (Compute)(((AsyncResult)ar).AsyncDelegate);
    int result = c.EndInvoke(ar);
    string s = (string)ar.AsyncState;
    Console.WriteLine("{0} contains {1} chars", s, result);
  }
  static void Main()
  {
    Compute c = new Compute(TimeConsumingFunction);
    AsyncCallback ac = new AsyncCallback(ViewResultFunction);
    string s1 = "Christopher";
    string s2 = "Nolan";
    IAsyncResult ar1 = c.BeginInvoke(s1, ac, s1);
    IAsyncResult ar2 = c.BeginInvoke(s2, ac, s2);
    Console.WriteLine("Ready");
    Console.Read();
  }
}

Výstupem je:

[ukázka kódu]
Ready
Christopher contains 11 chars
Nolan contains 5 chars