Życie asynchroniczne, czyli o async/await

Przyszedł czas na post nieco bardziej zahaczający o .Net Core. Spojrzymy dziś na przetwarzanie asynchroniczne w C#. Pozwoli nam to na wykonywanie pewnych procesów w tle, bez konieczności blokowania wątku naszej aplikacji. Przetwarzanie takie przydaje się między innymi w pracy nad plikami – na przykład odczytem i zapisem plików, które wykonywane są w osobnym wątku, dzięki czemu użytkownik korzystający z naszej aplikacji może wykonywać w niej inne czynności. Używane są one również w działaniach na bazie danych, pobieraniem i wysyłaniem danych do jakiegoś API oraz w komunikacji z WCF.

W C# asynchroniczność opiera się o słowa kluczowe async oraz await. Pierwsze, async, jest słowem kluczowym używanym przy funkcjach, stawiane przed typem, jaki ma ona zwrócić. Metody oznaczone słowem async musza mieć jeden z trzech typów:
-void;
-Task;
-Task (gdzie typem zwracanym przez naszą funkcje jest TResult).

Słowo Task można rozumieć przez swego rodzaju zadanie, które zostanie wywołane po zakończeniu działania naszej funkcji. My w naszym programie oczekujemy na nie poprzez słówko await. Używamy go przed wykonaniem funkcji zwracającej Task. Należy zauważyć, iż słowo await musi znaleźć się w ciele metody oznaczonej słowem async, gdyż inaczej zostanie ona potraktowana jak zwykła metoda synchroniczna. Sądzę, iż na razie wystarczy nam teorii. Jest to mało, jednak async/await jest bardzo obszernym tematem, na który można poświęcić cały cykl, dlatego, aby lepiej pojąć o czym teraz piszę zademonstruję przykładową aplikację konsolową. Założenia naszej aplikacji są następujące:
-jest to aplikacja konsolowa;
-użytkownik ma do wyboru 3 wykonujące się długo metody: SlowAsyncFibonacci, SlowAsyncFactorial, SlowAsyncSum(dla uproszczenia metody te zwracają na sztywno jakąś wartość, a ich “długość” to po prostu funkcja Thread.Wait());
-wywołanie jednej metody nie blokuje aplikacji.

Pokażę teraz naszego Maina:

public const string MainMenu = "Choose one:\n" +
                                        "1.SlowAsyncFibonacci\n" +
                                        "2.SlowAsyncFactorial\n" +
                                        "3.SlowAsyncSum\n" +
                                        "0.Exit";

public const string Error = "You choose the wrong number\n";

static void Main(string[] args)
        {
            string choose;
            while (isWorking)
            {
                Console.WriteLine(MainMenu);
                choose = Console.ReadLine();
                switch (choose)
                {
                    case "1":
                        HandleFibonacciAsync();
                        break;
                    case "2":
                        HandleFactorialAsync();
                        break;
                    case "3":
                        HandleSumAsync();
                        break;
                    case "0":
                        isWorking = false;
                        break;
                    default:
                        Console.WriteLine(Error);
                        break;
                }
            }
        }

Jest to prosta pętla while, która działa, jeśli flaga isWorking jest ustawiona na true. Wybór metody to po prostu zwykły switch, który w zależności od podanej wartości uruchomi konkretną metodę:

public static async void HandleFibonacciAsync()
        {
            Console.WriteLine("Fibonacci start");
            var result = await Task.Run(() => GetFibbonacciAsync());
            Console.WriteLine("Fibonacci result is: " + result);
        }

        public static int GetFibbonacci()
        {
            for (int i = 0; i  GetFactorialAsync());
            Console.WriteLine("Factorial result is: " + result);
        }

        public static int GetFactorial()
        {
            for (int i = 0; i  GetSumAsync());
            Console.WriteLine("Sum result is: " + result);
        }

        public static int GetSum()
        {
            for (int i = 0; i < 11; i++)
            {
                Thread.Sleep(1300);
            }
            return SumResult;
        }

Jak widzimy, każdy handler nie zwraca nam niczego, wywołuje po prostu funkcję, na wykonanie której oczekujemy słowem await. Nasze metody GetFibonacci, GetFactorial oraz GetSum nie zwracają nam Task tylko sam int, więc wywołanie takich metod odbywa się poprzez:

await Task.Run(() => NotTaskReturningMethod());

Gdyby nasza metoda zwracała po prostu Task moglibyśmy użyć następującego kodu:

await TaskReturningMethod();

Gdyby żaden z naszych handlerów nie posiadał w swoim ciele słowa await, podczas budowania otrzymalibyśmy ostrzeżenie, że nasza metoda mimo słowa async będzie zwykłą metodą synchroniczną. Możemy teraz uruchomić nasz program, którego działanie będzie wyglądało następująco:

Screen Shot 2017-09-02 at 18.51.31

Mimo uruchomienia funkcji trwającej długo, mamy możliwość dalszego wyboru w naszym menu. Jeśli chcemy zobaczyć, jak nasze funkcje działają obok siebie, wystarczy w każdym forze dodać odpowiedni dla nich tekst w Console.WriteLine. Zachęcam do samodzielnego spróbowania ;>

To wszystko jeśli chodzi o podstawy async/await. Przetwarzania asynchronicznego nie należy się bać, jest ono bardzo przydatnym i pożytecznym narzędziem, ale należy używać go z umiarem, ponieważ zabawa z wątkami jest równie ciekawa, co niebezpieczna ;>

Do przeczytania niedługo ;>

Advertisements

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 )

Google photo

You are commenting using your Google 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 )

Connecting to %s