W najbliższych postach zajmę się wielowątkowością w C#. Jednak zanim przejdę do "prawdziwych" watków (z przestrzeni System.Threading), najpierw asynchroniczne delegaty.
Asynchroniczne delegaty
Delegata można utożsamiać ze wskaźnikiem na funkcję z zachowaniem bezpieczeństwa typów. Takiego delegata można wywołać synchronicznie i asynchronicznie. Asynchroniczne wywołanie delegata daje takie same rezultaty jak utworzenie nowego wątku a jest nawet prostrze!
Jak się okaże, wystarczy przypisać delegatowi pewną funkcję i zamiast wywołać delegata jak zwykła funkcję użyta zostanie metoda BeginInvoke().Rzućmy okiem na poniższy programik:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace AsyncDelegate { public delegate int BinaryDelegate(int x, int y); class Program { private static bool isDone = false; static void Main(string[] args) { Console.WriteLine("**** Asynchroniczne delegaty ****"); Console.WriteLine("Main() wywołana na wątku {0}.", Thread.CurrentThread.ManagedThreadId); BinaryDelegate b = new BinaryDelegate(Add); IAsyncResult theAsRes = b.BeginInvoke(10, 10, new AsyncCallback(AddComplete), "Dzięki za współpracę!"); while (!isDone) { Thread.Sleep(1000); Console.WriteLine("WORKING..."); } //Console.WriteLine("WAITING"); Console.ReadLine(); } static int Add(int x, int y) { Console.WriteLine("Add() wywołana na wątku {0}.", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(5000); return x + y; } static void AddComplete(IAsyncResult theAsRes) { Console.WriteLine("AddComplete() wywołane na wątku {0}.", Thread.CurrentThread.ManagedThreadId); Console.WriteLine("Dodawanie skończone"); Console.WriteLine((string)theAsRes.AsyncState); isDone = true; } } }
Teraz po kolei wszystko sobie wyjaśnimy.
- Jak widać delegat nie musi znajdować się wewnątrz klasy, może być poza nią.
- Do podejrzenia identyfikatora wątku używamy Thread.CurrentThread.ManagedThreadId
- Metoda BeginInvoke zawsze zwraca obiekt implementujący interfejs IAsyncResult. Ten obiekt to pewnego rodzaju sprzężenie pozwalające wątkowi wywołującemu (czyli temu głównemu) uzyskać wynik asynchronicznego wywołania metody w późniejszym czasie (np przy pomocy EndInvoke(), którą tutaj pominąłem).
- Do synchronizacji użyłem tutaj zmienną typu bool. Nie jest to dobra praktyka lecz dla tego przykładu w zupełności wystarczająca. (Nie jest dobra dlatego że każdy z wątków ma do niej jednoczesny dostęp... ale o tym później)
- Pętla co sekundę sprawdza czy dodawanie już się zakończyło. Zapobiega ona zakończeniu programu zanim wątek poboczny obliczy wynik.
Jednak pozostało jeszcze do rozstrzygnięcia coś dziwnego.
Normalnie wywołując delegata (czy to tak jak funkcję czy za pomocą invoke) podawalibyśmy 2 parametry (agrumenty funkcji "połaczonych" z delegatem). Jednak dla BeginInvoke potrzeba 4 parametrów.
- Za pomocą pierwszego parametru podaje się funkcję, która zostanie wywołana po zakończeniu wątku pobocznego.
- Drugi parametr jest typu Object. Pozwala przesłać dowolny obiekt do wybranej wcześniej funkcji.
I tak:
Tworząc nowego delegata AssyncCallback i podając mu jako parametr nazwę funkcji AddComplete (która przyjmuje obiekt IAsyncResult) mamy pewność że funkcja zostanie wywołana natychmiast po zakończeniu wątku pobocznego. Dodatkowo dzięki czwartemu parametrowi, przy pomocy IAsyncResult theAsRes możemy "wyciągnąć" dodatkowy argument przesłany do funkcji przez rzutowanie jego właściwości AsyncState na string.
Jeśli już robisz synchronizację w taki sposób to czemu nie wykorzystać theAsRes.IsCompleted? Jaki jest cel wprowadzania dodatkowej zmiennej typu bool?
OdpowiedzUsuńPaweł