Wątek jest definiowany jako ścieżka wykonywania programu. Każdy z wątków definiuje unikalny przepływ sterowania. Jeśli aplikacja wymaga skomplikowanych i czasochłonnych czynności, wówczas można ustawić różne ścieżki wykonywania części programu tak, aby każdy wątek wykonywał określoną pracę.

Wątki są lekkimi procesami. Jednym z przykładów użycia jest operacja projektowania współczesnych systemów operacyjnych. Korzystanie z wątków pozwala zaoszczędzić straty mocy obliczeniowej procesora oraz pozwala na wzrost wydajności działania aplikacji.

We wszystkich powyżyszch przykładach pisaliśmy programy, które działały jako pojedynczy proces, który uruchamiał instancję naszej aplikacji. Jednakże, pisząc programy w taki sposób jesteśmy w stanie wykonywać tylko jedno zadanie w danym momencie. Aby wykonywać więcej niż jedno zadanie w danym czasie należy program podzielić na mniejsze wątki.


Cykl życia wątku

Cykl życia wątku zaczyna się kiedy obiekt klasy System.Threading.Thread zostaje utworzony oraz kończy się, gdy wątek jest przerywany lub zakończy wykonywanie.

Poniżej przedstawiono różne stany w cyklu życia wątku:

  • Stan przed rozpoczęciem: sytuacja w której doszło do utworzenia instancji wątku ale metoda Start() nie została wywołana;
  • Stan gotowości: sytuacja w której wątek jest gotowy do działania i oczekuje na cykl procesora;
  • Stan wstrzymania: wątek nie zostanie wykonany, gdy:
    • tryb uśpienia został wywołany;
    • metoda Wait() została wywołana;
    • jest zablokowany przez operacje I/O;
  • Stan martwy: sytuacja w której wątek zakończył wykonywanie lub został przerwany.


Główny wątek

W języku C# klasa System.Threading.Thread pozwala na pracę z wątkami. Pozwala na tworzenie oraz dostęp do poszczególnych wątków w wielowątkowej aplikacji. Pierwszy wątek do wykonania w procesie nazywany jest głównym wątkiem (Main).

Gdy program zaczyna wykonywanie, główny wątek jest tworzony automatycznie. Wątki tworzone przy użyciu klasy Thread nazywane są wątkami pochodnymi głównego wątku. Dostęp do wątku można uzyskać dzięki właściwości CurrentThread klasy Thread.

Poniższy program pokazuje wykonanie głównego wątku:

using System;
using System.Threading;
namespace MultiThreading
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread th = Thread.CurrentThread;
            th.Name = "GłównyWątek";
            Console.WriteLine("To jest: {0}", th.Name);
            Console.ReadKey();
            // Wynik działania programu
            // To jest: GlównyWatek
        }
    }
}

Właściwości oraz metody klasy Thread

Poniżej tabela zawierająca najczęściej używane właściwości klasy Thread:

Właściwość Opis
CurrentContext pobiera bieżący kontekst w którym jest wykonywany wątek
CurrentCulture pobiera lub ustawia ustawienia regionalne dla obecnego wątku
CurrentPrinciple pobiera lub ustawia zabezpieczenia wątku (dla bezpieczeństwa opartego na rolach)
CurrentThread pobiera obecnie uruchomiony wątek
CurrentUICulture pobiera lub ustawia ustawienia regionalne używane przez Menadżera zasobów (Resource Manager) aby mieć podgląd do ustawień regionalnych w trakcie wykonywania programu
ExecutionContext pobiera obiekt ExecutionContext, który zawiera informacje na temat różnych kontekstów danego bieżącego wątku
IsAlive pobiera wartość wskazującą na stan realizacji bieżącego wątku
IsBackground pobiera lub ustawia wartość wskazującą czy dany wątek jest wątkiem tła
IsThreadPoolThread pobiera wartość wskazującą czy danych wątek należy do póli zarządzanych wątków
ManagedThreadId pobiera unikatowy identyfiaktor bieżącego zarządzanego wątku
Name pobiera lub ustawia nazwę wątku
Priority pobiera lub ustawia wartość wskazującą na priorytet bieżącego wątku
ThreadState pobiera informację dotyczącą stanu bieżącego wątku

Poniżej lista najczęściej używanych metod klasy Thread:

Właściwość Opis
public void Abort() Zwraca wyjątek ThreadAbortException w bieżącym wątku rozpoczynając proces przerywania wątku. Wywołanie tej metody zwykle kończy wątek
public static LocalDataStoreSlot AllocateDataSlot() Metoda przeznacza anonimowe gniazdo danych na wszystkie wątki. Dla lepszej wydajności należy używać pól, które są oznaczone atrybutem ThreadStaticAttribute
public static LocalDataStoreSlot AllocateNamedDataSlot(string name) Metoda przeznacza nazwane gniazdo danych na wszystkie wątki. Dla lepszej wydajności należy używać pól, które są oznaczone atrybutem ThreadStaticAttribute
public static void BeginCriticalRegion() Metoda powiadamia, ze wykonywanie programu ma zamiar przejść do obszaru kodu w którym przerwanie wątku badź nieobsługiwany wyjątek może mieć zagrożenie dla innych zadań wykonywanych w danej aplikacji
public static void BeginThreadAffinity() Metoda powiadamia o tym, że zarządzany kod zamierza wykonać instrukcje w zależności od tożsamości aktualnego fizycznego wątku system operacyjnego
public static void EndCriticalRegion() Metoda powiadamia, że wykonanie programu ma zamiar przejść do bloku kodu w którym przerwanie wątku lub nieobsługiwany błąd ogarniczania się jedynie do obecnego zadania
public static void EndThreadAffinity() Metoda powiadamia, że zarządzany kod zakończył wykonywanie instrukcji w zależności od tożsamości aktualnego fizycznego wątku systemu operacyjnego
public static void FreeNamedDataSlot(string name) Metoda eliminuje powiązania pomiędzy nazwą a gniazdem (slot) dla wszystkich wątków w procesie. Dla lepszej wydajności należy używać pól, które są oznaczone atrybutem ThreadStaticAttribute
public static Object GetData(LocalDataStoreSlot slot) Metoda pobiera wartość z określonego gniazda bieżącego wątku w bieżącej domenie
public static AppDomain GetDomain() Metoda zwraca bieżącą domenę w której wątek jest wykonywany
public static LocalDataStoreSlot GetNamedDataSlot(string name) Metoda służy do wyszukiwania nazwanego gniazda danych. Dla lepszej wydajności należy używać pól, które są oznaczone atrybutem ThreadStaticAttribute
public void Interrupt() Metoda służy do przerywania wątku, który jest w stanie WaitSleepJoin
public void Join() Metoda służy do blokowania wątku wywołującego aż wątek zostanie przerwany podczas jednoczesnego kontynuowania standardowych operacji COM oraz SendMessage
public static void MemoryBarrier() Synchronizuje dostęp do pamięci
public static void ResetAbort() Anuluje lub przerywa żądanie dla bieżącego wątku
public static void SetData(LocalDataStoreSlot slot, Object data) Metoda ustawia dane w określonym gnieździe aktualnie uruchomionego wątku dla bieżącej domeny. . Dla lepszej wydajności należy używać pól, które są oznaczone atrybutem ThreadStaticAttribute
public void Start() Metoda rozpoczynająca wątek
public static void Sleep(int millisecondsTimeout) Metoda pozwala na zatrzymanie wykonania wątku na określony okres czasu
public static void SpinWait(int iterations) Metoda powoduje, że wątek musi czekać określoną liczbę razy zdefiniowaną przez parameter iteracji
public static byte VolatileRead(ref byte address)
public static double VolatileRead(ref double address)
public static int VolatileRead(ref int address)
public static Object VolatileRead(ref Object address)
Odczytuje wartość pola. Wartość ta jest ostatnią wartością zapisaną przez którykolwiek procesor, bez względu na liczbę procesorów lub stan pamięci podręcznej procesora. Metoda ta ma różne formy przeciążenia. Tylko niektóre z nich są podane powyżej
public static void VolatileWrite(ref byte address,byte value)
public static void VolatileWrite(ref double address, double value)
public static void VolatileWrite(ref int address, int value)
public static void VolatileWrite(ref Object address, Object value)
Zapisuje wartość pola natychmiast, dlatego wartość ta jest widoczna dla wszystkich procesorów komputera. Metoda ta ma różne formy przeciążenia. Tylko niektóre z nich są podane powyżej
public static bool Yield() Metoda przerywająca wątek w celu wykonania innego wątku, który jest gotowy do uruchomienia na obecnym procesorze. System operacyjny wybiera wątek do przerwania. Jeżeli nie będzie innego wątku niż obecny wykonywanie będzie kontynuowane


Tworzenie wątków

Wątki są tworzone poprzez rozszerzenie klasy Thread. Rozszerzona klasa Thread wywołuje metodę Start(), aby rozpocząć wykonywanie wątku pochodnego. Poniżej przykład pokazujący koncepcję używania wątków:

using System;
using System.Threading;
namespace MultiThreadingCreate
{
    class Program
    {
        public static void CallToChildThread()
        {
            Console.WriteLine("Wątek pochodny wystartował");
        }
        static void Main(string[] args)
        {
            ThreadStart ts = new ThreadStart(CallToChildThread);
            Console.WriteLine("Główny wątek: Tworzenie wątku pochodnego");
            Thread pochodnyWatek = new Thread(ts);
            // Uruchamiamy wątek pochodny
            pochodnyWatek.Start();
            Console.ReadKey();
            // Wynik działania programu
            // Glówny watek: Tworzenie watku pochodnego
            // Watek pochodny wystartowal
        }
    }
}

Zarządzanie wątkami

Klasa Thread dostarcza nam metody pozwalające na zarządzanie wątkami.

Poniższy przykład pokazuje przykład użycia metody Sleep(), która pozwala powstrzymać wykonywanie wątka na pewien określony okres czasu:

using System;
using System.Threading;
namespace MultiThreadingSleep
{
    class Program
    {
        public static void CallToChildThread()
        {
            Console.WriteLine("Wątek pochodny wystartował");
            // Zatrzymamy wykonanie wątku na 5 sekund - 5000 milisekund
            int sleepFor = 5000;
            Console.WriteLine("Wątek zostanie zatrzymany na: {0} sekund", sleepFor / 1000);
            Thread.Sleep(sleepFor);
            Console.WriteLine("Wznowienie wykonywania wątku");
        }
        static void Main(string[] args)
        {
            ThreadStart ts = new ThreadStart(CallToChildThread);
            Console.WriteLine("Główny wątek: Tworzenie wątku pochodnego");
            Thread pochodnyWatek = new Thread(ts);
            // Uruchamiamy wątek pochodny
            pochodnyWatek.Start();
            Console.ReadKey();
            // Wynik działanie programu
            // Glówny watek: Tworzenie watku pochodnego
            // Watek pochodny wystartowal
            // Watek zostanie zatrzymany na: 5 sekund
            // Wznowienie wykonywania watku
        }
    }
}

Niszczenie wątków

Metoda Abort() jest używana do niszczenia wątków.

Środowisko wykonawcze przerywa wątek rzucając wyjątek ThreadAbortException. Wyjątek ten jest specjalnym wyjątkiem, który może być wyłapany przez kod aplikacji, ale jest ponownie rzucony pod koniec wykonywania bloku catch chyba, że zostanie wywołana metoda ResetAbort().

Poniższy przykład pokazuje powyższą koncepcję:

using System;
using System.Threading;
namespace MultiThreadingDestroy
{
    class Program
    {
        public static void CallToChildThread()
        {
            try
            {
                Console.WriteLine("Wątek pochodny wystartował");
                // wykonanie jakichś instrukcji, np. odliczanie do 10
                for (int i = 0; i < 10; i++)
                {
                    // przerwanie wykonywania wątku na okreslony okres czasu
                    Thread.Sleep(250);
                    Console.WriteLine(i);
                }
                Console.WriteLine("Wątek został wykonany");
            }
            catch (ThreadAbortException e)
            {
                Console.WriteLine("Wyjątek: ThreadAbortException");
            }
            finally
            {
                Console.WriteLine("Nie można złapać wyjątku");
            }
            
        }
        static void Main(string[] args)
        {
            ThreadStart ts = new ThreadStart(CallToChildThread);
            Console.WriteLine("Główny wątek: Tworzenie wątku pochodnego");
            Thread pochodnyWatek = new Thread(ts);
            // Uruchamiamy wątek pochodny
            pochodnyWatek.Start();
            // zatrzymanie głównego wątku
            Thread.Sleep(2000);
            // przerwanie wątku pochodnego
            Console.WriteLine("Główny wątek: przerwanie wątku pochodnego");
            pochodnyWatek.Abort();
            Console.ReadKey();
            // Wynik działania programu
            // Glówny watek: Tworzenie watku pochodnego
            // Watek pochodny wystartowal
            // 0
            // 1
            // 2
            // 3
            // 4
            // 5
            // 6
            // Glówny watek: przerwanie watku pochodnego
            // Wyjatek: ThreadAbortException
            // Nie mozna zlapac wyjatku
        }
    }
}