Home > C#, C# 5, Visual Studio > [PL] Ostra jazda na 5 – C# 5 (await i async)

[PL] Ostra jazda na 5 – C# 5 (await i async)

November 14, 2010 Leave a comment Go to comments

Rok temu na produkcję wkroczył .NET Framework 4, a wraz z nim język C# 4 i jego najpopularniejsze słowo kluczowe dynamic. Po niecałym roku dostępny jest już między innymi CTP części wersji C# 5, a z nim kilka nowych słówek kluczy – async i await pomagających w tworzeniu aplikacji wielowątkowych, interaktywnych. Sprawdźmy czym są i jak działają.

Do naszej zabawy potrzebować będziemy Visual Studio 2010 i pakiet Visual Studio Async CTP. Sama instalacja trochę trwa, ale po niej Async zintegruje się z Visual Studio dodając podpowiedzi do IntelliSense, a dodatkowo zainstalowane zostaną przykłady wykorzystujące nowe elementy.

Spróbujemy teraz zbudować prostą aplikację, która będzie zawierała nowe słowa kluczowe async i await. Przykładowo mamy aplikację, która importuje listę rekordów do bazy danych, lista może być dosyć długa, aplikacja zaiwera UI które pokazuje status importu, co więcej UI nie może się ‘zamulać’ podczas importu. Oczywiście, przychodzi kilka różnych rozwiązań, które są znane, ale spróbujmy utworzyć taką prostą aplikację wykorzystując do tego celu nowe elementy nadchodzącego C# 5.

W tym celu tworzymy aplikację WinForms .NET 4 w Visual Studio 2010. Po utworzeniu projektu dodamy referencję do AsyncCtpLibrary.dll, dzięki temu będziemy mogli skompilować kod.Adding reference to Async CTP dll

Aplikacja będzie zawierała jedynie przycisk start, napisz coś oraz listę, w której wyświetlane będą operacje (logi), całość będzie wyglądała mniejwięcej tak

Najpierw dodamy funkcję pomocniczą, pozwalająca nam dodać log do wyświetlanej listy logów – log zawsze będzie dodawany na samej górze listy logów

private void WriteLog(string message)
{
    statusList.Items.Insert(0,
        String.Format("{0}[{1}] {2}",
        DateTime.Now.TimeOfDay.ToString(),
        Thread.CurrentThread.ManagedThreadId,
        message));
}

następnie przypiszemy zdarzenie po naciśnięciu na przycisk Napisz coś, który będzie dodawał log tuż po naciśnięciu.

private void button3_Click(object sender, EventArgs e)
{
    WriteLog("coś");
}

A teraz najciekawsze, dodamy zdarzenie dla przycisuku Start, który będzie symulował pracę. Kod widoczny jest poniżej

private async void button1_Click(object sender, 
                                 EventArgs e)
{
  statusList.Items.Clear();
  statusBar.Maximum = ITERATIONS;
  statusBar.Value = 0;
  button1.Enabled = false;
  WriteLog("Startuję");
  for (int i = 1; i <= ITERATIONS; i++)
  {
    WriteLog(i.ToString());
      await TaskEx.Run(()=> {
      // wykonujemy jakąś operację numeryczną, 
      // która może trochę potrwać
      Thread.Sleep(1000);
    });
  }
  WriteLog("Stop");
  button1.Enabled = true;
}

Listę operacji, rekordów symuluje w tym przypadku pętla for, natomiast zadanie, które jest wykonywane symuluje funkcja Thread.Sleep. Kod wydaje się sekwencyjny, ponieważ mamy sekwencję poleceń, która ma być wywoływana wyjątkiem są słowa async, które znajduje się przy deklaracji procedury, oraz await znajdujący się przed TaskEx (TaskEx jest również dodany do Async CTP, później najprawdopodobniej zostanie zaimplemenowane w samej klasie Task). Po uruchomieniu naszej aplikacji i wciśnięciu przycisku Start, na liście logów co sekundę będzie pojawiał się log, natomiast w tym samym czasie możemy przyciskać sam przycisk Napisz coś.

Aplikacja nie zawiesza się, okno rysuje się poprawnie (nie otrzymujemy komunikatu brak odowiedzi), a co więcej nasz kod jest krótki i przejrzysty. Zastanówmy się natomiast jak to działa.

Jak to działa

Najpierw zobaczmy jak działa i do czego służą nowe słowa kluczowe. Dobrze wytłumaczyone jest to w poście Erica Lipperta [3].

async– jest swego rodzaju etykietą dla funkcji, procedury, która ukierunkowuje kompilator, że procedura zawiera przepływ dla await, (jest to odpowiednik interfejsu IEnumerable dla tworzenia kolekcji w .NET 2.0).

await – jeżeli zadanie, na które czekamy jeszcze się nie skończyło, to rejestrujemy resztę funkcji, która ma być wywołana i wychodzimy do funkcji wywołującej. Reszta zarejestrowanej funkcji zostanie wywołana po zakończeniu zadania (jest to odpowiednik słowa kluczowego yield). Aby użyć to słowo, wymagane jest oznaczenie funkcji/procedury za pomocą async (można oznaczyć metodę za pomocą async nie wykorzystując await, otrzymamy jednak warning, ale kod będzie działał synchronicznie).

Zobaczmy teraz jak wygląda to od strony samego CLR. W tym celu otwieramy nasz program przez .NET Reflector. Widzimy, że nasza procedura została zapisana w innej klasie/procedurze, a sam kod został dodatkowo obsłużony przez delegaty wygenerowane przez kompilator, w praktyce większość pracy wykonywana jest za nas. Async i await zostały zamienione przez kompilator na odpowiedni kod.

Podsumowanie

Zdecydowanie nowe elementy upraszczają tworzenie aplikacji wielowątkowych – odpowiednikiem async i await dla .NET (ale w innej dziedzinie) jest tworzenie enumeracji za pomocą słówka yield, dostępnego już od .NET 2.0 – pozwoliło to pozbyć się mozolnego tworzenia iteratorów, a praca stała się przyjemniejsza. Warto zainteresować się tym więcej i przeczytać sobie więcej na stronie [1], ciekawym dokumentem jest Aynchrony Whitepaper [2].

  1. November 14, 2010 at 19:10

    Fajny wpis. Co do tego ficzera to mam mieszane uczucia. Z jednej strony fajne. Z drugiej sam napisałeś, że to samo dało się już osiągnąć chociażby przez sprytne użycie yield (którego i tak wielu programistów nie rozumie i nie używa).

    Ładowanie czego się da do składni języka to nie jest dobra droga… bo na jej końcu jest potwór, który jest mało przewidywalny, skomplikowany i groźny. A niestety C# właśnie jest takie podejście. Nowe wersje co 1-2 lata i musi być kilka nowych konstrukcji językowych. To trochę tak jak (na innej płaszczyźnie) dyskusja między RISC a CISC – albo robimy małe proste kawałeczki z których każdy sobie zrobi co chce, albo robimy wielkie skomplikowane narzędzie i jest ich mega dużo.

    Tak czy siak to wszystko już się da od dawna w Lispie i Haskellu ;) Różnica jest taka, że tam nie trzeba wydawać nowej wersji języka co roku… Może kolejny wpis zrobisz właśnie o tym..? ;)

  2. LaM
    May 29, 2011 at 20:36

    @witek
    Tak masz rację ale zwróć uwagę na to , że świat programistów to już nie tylko geekowi “hakerzy” dla których ta praca to pasja. Większość developerów to niedzielni “koderzy” , których trzeba zachęcać do uczenia i do zmiany własnego warsztatu umiejętności poprzez właśnie takie wstawianie czegoś co już kiedyś było ale pod nową nazwą.

  3. AdamO
    December 22, 2013 at 11:29

    Ja jestem niedzielnym “koderem” i widzę w tym ficzerze nowe możliwości, jeśli chodzi o konwencję. Pisząc np. w LINQ możemy się zdecydować na preferowanie składni wyrażeń zapytań lub na metody rozszerzeń. Decydujemy się na to, co tworzy bardziej czytelny kod. Jeśli chodzi o wielowątkowość to nie przepadam za tworzeniem Threadów, a te słowa kluczowe znakomicie to ukryją. Nie znam niestety jeszcze słowa yield, więc cieszę się, że pojawiło się odniesienie.

    • December 23, 2013 at 19:28

      Najważniejsze, aby koder (czy to niedzielny, czy tygodniowy) wiedział jak dany ‘ficzer’ działa. Nie raz spotkałem się z użyciem LINQ w niewłaściwy sposób: IEnumerable zamiast IQueryable, czy też nadmiarowo – czasami prostsze foreach jest czytelniejsze – potem się dziwią ludzie, że aplikacja zamula. Powodzenia w poznawaniu .NETa!

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: