wtorek, 23 listopada 2010

O Task–ach słów kilka...

Jak wcześniej pisałem, w .NET 4 Microsoft dodał do Frameworka naprawdę potężne narzędzie do tworzenia aplikacji wielowątkowych. Bibliotekę tę można podzielić na dwie części – PLINQ, o której pisałem we wcześniejszych postach oraz TPL (Task Parallel Library), o której chciałbym napisać parę słów.

TPL, została oczywiście zbudowana na wcześniej istniejących elementach Frameworka, takich jak wątki i pula wątków (klasy Thread i ThreadPool) i  jak sama nazwa wskazuje, bazuje na zadaniach, czyli obiektach reprezentowanych przez klasę Task. Sama biblioteka została umieszczona w namespace System.Threading.Tasks. Sam sposób zarządzania wielowątkowością został znacznie uproszony. Używając wcześniejszych wersji .NET-a podstawową strukturą był wątek, który można sobie wyobrazić jako swego rodzaju “robotnika” - trzeba go ciągle nadzorować, pilnować, aby coś złego się z nim nie stało, itd. W przypadków zadań (Task) jest zupełnie inaczej – w tym przypadku patrzymy bardziej na to, co jest do wykonania, niż jak należy to zrealizować.

Tworzenie zadań

Koniec teorii, czas na trochę praktyki – zadanie tworzymy wywołując oczywiście odpowiedni konstruktor klasy Task – polecam przeglądnięcie manuala, gdyż istnieje kilka, jak nie kilkanaście konstruktorów, które dają szerokie spektrum możliwości. Aby uruchomić tak utworzone zadanie wołamy na wcześniej utworzonym obiekcie metodę Start. Zadanie możemy też za jednym zamachem utworzyć i wystartować używając do tego celu statycznego property na klasie Task o nazwie Factory i wołając na nim metodę StartNew, która podobnie jak konstruktora klasy Task ma kilkanaście różnych przeciążeń. Poniżej przykład prezentujący wcześniej opisany mechanizm tworzenia zadań:

// Najprostszy sposób utworzenia zadania
Task t1 = new Task(state => DoActionAsync(state), "state value");
// I wystartowanie go:
t1.Start();
// Utworzenie zadania i wystartowanie:
Task t2 = Task.Factory.StartNew(() => DoOtherAsyncAction());
Przewagą (jedną z wielu oczywiście :) ), jaką mają zadania nad wątkami jest możliwość zwrócenia wartości. Pozwala na to klasa Task<TResult> dziedzicząca po wcześniej opisanej klasie Task. Konstruktor tego typu jako parametr przyjmuje funkcję, która zwraca rezultat typu TResult. Do wyniku tak stworzonego zadania możemy uzyskać dostęp korzystając z property Result. O czym należy pamiętać, to że właściwość ta jest synchroniczna i jeśli wywołamy akcesor to będzie on czekał na zakończenie wątku zadania i dopiero wtedy zwróci wynik. Poniżej przykład:
// Utworzenie zadania, które zwróci wynik:
Task t3 = new Task(() => GenerateSum());
// Zadanie możemy też uruchomić w bieżącym wątku
t3.RunSynchronously();
// Zadanie zwracające wynik możemy utworzyć i od razu uruchomić:
Task t4 = Task.Factory.StartNew(() => FindWord());

// Przetwarzanie w wątku głównym...

// Odczytujemy wynik z uruchomionego zadania:
String word = t4.Result;
Czekanie na zakończenie zadania

Częstą sytuacją jest czekanie w głównym wątku zakończenie zadania/zadań, które zostały uruchomione asynchronicznie (np. kontynuacja przetwarzania zależy od wyniku zadania). Jeśli czekamy na pojedyncze zadanie, możemy na jego instancji zawołać metodę Wait. Klasa Task dostarcza także statycznych metod do czekania wszystkie zdania z kolekcji (WaitAll) lub na pierwsze zadanie, które się zakończy (WaitAny):

Task t1 = Task.Factory.StartNew(() => DoAsync());
//Czekamy na zakończenie zadania
t1.Wait();

Task[] tasks = new Task[] {
    Task.Factory.StartNew(() => FirstMethod()),
    Task.Factory.StartNew(() => SecondMethod()),
    Task.Factory.StartNew(() => ThirdMethod())
};

// Czekamy na zakończenie wszystkich zadań:
Task.WaitAll(tasks);

I na koniec ciekawostka – TPL bardzo dba o wydajność i stara się nie tworzyć nowych wątków jeśli nie jest to konieczne. Jeśli więc wywołamy metodę Wait, zanim zadanie zostanie uruchomione, to framework nie będzie tworzył osobnego wątku dla zadania, tylko wykona je w wątku, który wywołał metodę Wait.

Linki
  1. Task Parallel Library
  2. Ogólnie o zadaniach

Brak komentarzy:

Prześlij komentarz