Poprzednio wątki synchronizowane były za pomocą zmiennej bool. Nie jest to dobre rozwiązanie. Dodatkowo wątek główny musiał sprawdzać co pewien czas wartość tej zmiennej. Tutaj rozwiążemy to nieco bardziej optymalnie.
Stwórzmy w nowym konsolowym projekcie nową klasę:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Threads1 { class AddParams { public int a, b; public AddParams(int num1, int num2) { a = num1; b = num2; } } }
Zwykła klasa posiadająca publiczne pola a i b oraz konstruktor.
Narazie nie wnikamy po co ona jest.
Teraz napiszmy w głownej klasie programu nową metodę statyczną Add:
static void Add(object data) { Console.WriteLine("ID wątku metody Add(): {0}", Thread.CurrentThread.ManagedThreadId); AddParams ap = (AddParams)data; Console.WriteLine("{0} + {1} = {2}", ap.a, ap.b, ap.a + ap.b); }
Ta metoda będzie punktem wejścia wątku, czyli miejscem z którego wątek zacznie swoją pracę. Treść funkcji nie jest skomplikowana. Przyjmuje ona jeden parametr typu object. Ponieważ jest to zwykły obiekt, możemy przesłać do tej metody wsystko. Tak też zrobimy (po to właśnie została utworzona wcześniej klasa AddParams).
Czyli podsumowując:
- Mamy stworzoną metodę od której nowy wątek zacznie działanie
- Metoda przyjmuje parametr object który następnie rzutowany jest na wcześniej stworzoną klasę, z której wyciągane są parametry i wykonywane jest dodawanie.
Tworzenie nowego wątku
Dodajmy jeszcze minimalne uśpienie wątku (dla testu). Nie jest ono potrzebne, lecz pomoże zrozumieć istotę synchronizacji.
static void Add(object data) { Console.WriteLine("ID wątku metody Add(): {0}", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(100); AddParams ap = (AddParams)data; Console.WriteLine("{0} + {1} = {2}", ap.a, ap.b, ap.a + ap.b); }
Utwórzmy teraz nowy wątek. Metoda Main() aplikacji wygląda następująco:
static void Main(string[] args) { AddParams ap = new AddParams(10, 10); Thread t = new Thread(new ParameterizedThreadStart(Add)); t.Start(ap); Console.WriteLine("Finished"); Console.ReadLine(); }
Nowy wątek to obiekt typu Thread. W konstruktorze tego obiektu podajemy nowy obiekt ParametrizedThreadStart do którego konstruktora podajemy naszą metodę Add.
Dzięki temu że jest to obiekt ParametrizedThreadStart możliwe jest w następnej linii podanie obiektu z parametrami: t.Start(ap).
Tutaj uwidoczni się teraz problem synchronizacji. Gdyby nie Console.ReadLine w głownej metodzie Main() to program zamknął by się natychmiast. Tutaj pokazane przykładowe działanie:
Synchronizacja wątków
Aby wątek główny czekał na wątek poboczny do chwili jego zakończenia zdefiniujmy statyczny obiekt AutoResetEvent przed metodą Main():
private static AutoResetEvent waitHandle = new AutoResetEvent(false);
Przy konstruktorze podajemy wartość false. Oznacza to że nie przesłano jeszcze powiadomienia o zakończenia wątku pobocznego.
Teraz jeśli chcemy aby wątek główny rozpoczął oczekiwanie na wątek poboczny dopiszmy:
waitHandle.WaitOne();
Teraz w funkcji Add(..) aby poinformować o zakończeniu pracy wystarczy dopisać:
waitHandle.Set();
A efekt działania programu po tej zmianie:
Jak widać wątek główny zakończył się natychmiast po zakończeniu wątku pobocznego.
A teraz cały kod dla jasności:
Program.cs:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace Threads1 { class Program { private static AutoResetEvent waitHandle = new AutoResetEvent(false); static void Main(string[] args) { AddParams ap = new AddParams(10, 10); Thread t = new Thread(new ParameterizedThreadStart(Add)); t.Start(ap); waitHandle.WaitOne(); Console.WriteLine("Finished"); Console.ReadLine(); } static void Add(object data) { Console.WriteLine("ID wątku metody Add(): {0}", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(100); AddParams ap = (AddParams)data; Console.WriteLine("{0} + {1} = {2}", ap.a, ap.b, ap.a + ap.b); waitHandle.Set(); } } }
AddParams.cs:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Threads1 { class AddParams { public int a, b; public AddParams(int num1, int num2) { a = num1; b = num2; } } }