Podejście do asynchroniczności w .NET

Każdy ostatnio zachwyca się największą nowością jaka przyszła z .NET Framework 4.5, czyli słówkach kluczowych async i await. W poniższym poście postaram się przedstawić do czego służą i czy na prawdę tak bardzo ułatwiają pracę z metodami asynchronicznymi.

Na początku chciałbym przyjrzeć się na dwóch wzorcach pracy z asynchronicznością, które istniały wcześniej. Mowa tu o Asynchronous Programming Model (APM) oraz Event-based Asynchronous Pattern (EAP)

Asynchronous Programming Model

Zwany również jako Begin/End pattern. Polegał na tworzeniu powiązanych ze sobą metod BeginMehtodName oraz EndMethodName. Przy czym BeginMethodName rozpoczynała metodę asynchorniczną oraz zwracała obiekt implementujący interfejs IAsyncResult natomiast EndMethodName czekała aż metoda się skończy. Co nam to dawało? Przede wszystkim możliwość wykonania innych operacji między wywołaniem Begin a End. Trzeba było jednak samemu się domyślić ile operacji możemy zrobić, żeby po wywołaniu End nie czekać znów zbyt długo. W metodzie Begin mogliśmy też podać delegata AsyncCallback, który wywoływał się kiedy operacja się skończyła.

Przykładem takiego modelu może być odczyt danych ze strumienia.

var buff = new byte[1024];
var asyncResult = stream.BeginRead(buff, 0, 1024, ReadComplete, null);

//..do some other stuff

stream.EndRead(asyncResult);

Event-based Asynchronous Pattern

Model oparty na zdarzeniach był już łatwiejszy do zaimplementowania. Generował natomiast bardzo wiele kodu. Model ten polega na tym, że dla każdej metody: MethodNameAsync tworzony był delegat MethodNameCompletedEventHandler, przyjmujący obiekt typu MethodNameEventArgs oraz tworzone było zdarzenie MethodNameCompleted.

class MyClass
{
   public void ReadAsync(byte[] buffer, int offset, int count) {
      //.. do some asynchronously
      // then call ReadCompleted
   }
   public event ReadCompletedEventHandler ReadCompleted;
}

public delegate void ReadCompletedEventHandler(object sender, ReadEventArgs eventArgs);

public class ReadEventArgs : AsyncCompletedEventArgs
{
   // class body
}

Dodatkowo, jeśli chcieliśmy aby metoda, którą przypisujemy do zdarzenia MethodNameCompleted robiła coś na wątku UI, musieliśmy tworzyć Dispatchera, aby jej to umożliwić.

Task-based Asynchronous Pattern

Tak więc jak działa model oparty na Taskach? Powyższy przykład moglibyśmy zapisać tak:

public class MyClass
{
   public Task ReadAsync(byte[] buffer, int offset, int count)
   {
      //...
   }
}

Różnić się będzie jedynie wywołanie takiej metody. Ponieważ metoda ReadAsync zwraca obiekt typu Task, możemy więc użyć przy niej słówka await, które działa mniej więcej tak:

Załóżmy, że mamy taką klasę AsyncClass z metodą GetRandomNumberAsync, która to metoda losuje jakąś liczbę całkowitą czeka wylosowaną ilość sekund, po czym zwraca tą liczbę.

internal class AsyncClass
{
   public Task GetRandomNumberAsync()
   {
      return Task.Run(() =>
      {
         var rand = new Random();
         var randNum = rand.Next(20);
         Thread.Sleep(TimeSpan.FromSeconds(randNum));
         return randNum;
      });
   }
}

Wywołanie tej metody wygląda następująco:

static async void Foo()
{
   var asyncObject = new AsyncClass();
   var num = await asyncObject.GetRandomNumberAsync(); //(1)
   Console.WriteLine(num); //(2)
}

W linii (1) metoda używa słówka await, więc kompilator automatycznie przerzuca to co wykonuje się po tej linijce do oddzielnej metody, która wywoła się dopiero kiedy metoda GetRandomNumberAsync zwróci jakiś wynik. Dodatkowo to co wywoła się w lini (2) zostanie wywołane w wątku UI, więc możemy się z tego poziomu odwoływać do elementów okna, bez potrzeby uruchamiania Dispatchera.

Jak widać, wszystkie powyższe podejścia do asynchroniczności są dostępne i dalej używane w .NET Framework 4.5. Ostatnie podejście skierowane jest specjalnie do aplikacji graficznych, w których metody przeważnie powodują odświeżenie okna wyników. Dzięki async i await możemy pozbyć się bardzo dużej ilości niepotrzebnego kodu.

Mam nadzieję, że już wiesz, czym różnią się te trzy podejścia i jak z nich korzystać. Jeśli chciałbyś dowiedzieć się więcej o asynchroniczności w .NET polecam bardzo lekturę dokumentu Task-based Asynchronous Pattern, stworzonego przez Microsoft, specjalnie aby wytłumaczyć nowe podejście do asynchroniczności.