fbpx

C# async await – podsatawy

Słyszałeś o konstrukcji C# async await?

Jeżeli zaczynasz przygodę z programowaniem, to założę się, że większość programów, które miałeś okazję stworzyć działa w sposób synchroniczny. Co to znaczy? To znaczy, że program wykonuje po kolei linijka po linijce. I w dodatku robi to w jednym wątku. W przypadku programów konsolowych nie stanowi to zwykle większego problemu. Ale gdy zaczniesz tworzyć bardziej skomplikowane aplikacje, np. odpytujące zewnętrzne serwisy, to będziesz musiał poznać konstrukcję języka C# async await.

Programowanie asynchroniczne – wielowątkowe

Alternatywą jest programowanie asynchroniczne. Tutaj też linijki kody wykonywane są po kolei, ale możemy skorzystać z wielu wątków. Programy wielowątkowe stały się modne w momencie gdy na rynku pojawiły się procesory wielordzeniowe. Dzisiaj takie procesory są standardem i każdy procesor Intel Core i3, i5, i7 posiada więcej niż 1 rdzeń. Dlatego programy wielowątkowe pozwolą wykorzystać potencjał tych procesorów. W rezultacie programy mogą być szybsze i wykonywać więcej operacji (bo program jest w stanie działać na kilku rdzeniach procesora jednocześnie).

Słowa kluczowe async i await

Jedną z podstawowych rzeczy, które należy poznać jest programowanie asynchroniczne. Dzięki odpowiedniemu użyciu słów kluczowych async i await będziesz w stanie przyspieszyć Twoje programy. A jeśli tworzysz aplikacje Windows Forms albo WPF to dzięki konstrukcji języka C# async await, zapomnisz o zawieszających się oknach kiedy program wykonuje jakąś operację (na przykład gdy pobiera dane z bazy danych).

Zatem dowiedzmy się co oznaczają słowa async oraz await.

async – tym słowem oznaczamy metodę, która jest asynchroniczna. Pamiętaj, że samo wywołanie takiej metody nie będzie skutkowało utworzeniem osobnego wątku. Aby tak się stało to w metodzie musi być wywołanie innej metody asynchronicznej.

await – to słowo kluczowe towarzyszy wywołaniu metody asynchronicznej. Zawsze gdzie występuje to słowo program będzie czekał aż dana metoda się wykona i dopiero wtedy będzie wykonywał kolejne instrukcje. UWAGA: słowa await możesz użyć tylko i wyłącznie do wywołania metod asynchronicznych.

Przykład w języku C#

Najlepiej zobrazuje to poniższy przykład. Dzięki niemu zobaczysz ile wątków zostanie użytych w programie.

Spójrz na ten kod i zastanów się ile wątków powstanie w wyniku jego wykonania? (pamiętaj, że metoda asynchroniczna, oznaczona słowem kluczowym async jest synchroniczna do momentu wywołania innej metody asynchronicznej. W naszym przypadku jest to Task.Delay(500))

class Program
{
    static void Main(string[] args)
    {
        Program p = new Program();
        p.Run();
    }
    private void Print(string txt)
    {
        string dateStr = DateTime.Now.ToString("HH:mm:ss.fff");
        Console.WriteLine($"{dateStr} Thread #{Thread.CurrentThread.ManagedThreadId}\t{txt}");
    }
    private void Run()
    {
        Print("Program Start");
        Experiment().Wait();
        Print("Program End. Press any key to quit");
        Console.Read();
    }
    private async Task Experiment()
    {
        Print("Experiment code is synchronous before await");
        await Task.Delay(500);
        Print("Experiment code is asynchronous after first await");
    }
}

Najlepiej skopiuj ten kod i wklej go do Visual Studio. Uruchom i postaraj się zrozumieć wynik.

Źródło i wyjaśnienie tego kodu znajdziesz na stronie:

https://stackoverflow.com/questions/27265818/does-the-use-of-async-await-create-a-new-thread

Kod bez użycia async i await

Zanim zobaczysz jak użyć słów async i await zapoznaj się z poniższym programem. Możesz go wkleić do Visual Studio i na pewno zadziała.

using System;
using System.Threading.Tasks;
namespace ConsoleAppAsyncAwait
{
    public class Coffee { }
    public class Egg {}
    public class Bacon { }
    public class Toast { }
    public class Juice { }
    class Program
    {
        static void Main(string[] args)
        {
            Coffee cup = PourCoffee();
            Console.WriteLine("coffee is ready");
            Egg eggs = FryEggs(2);
            Console.WriteLine("eggs are ready");
            Bacon bacon = FryBacon(3);
            Console.WriteLine("bacon is ready");
            Toast toast = ToastBread(2);
            ApplyButter(toast);
            ApplyJam(toast);
            Console.WriteLine("toast is ready");
            Juice oj = PourOJ();
            Console.WriteLine("oj is ready");
            Console.WriteLine("Breakfast is ready!");
        }
        private static Juice PourOJ()
        {
            Console.WriteLine("Pouring orange juice");
            return new Juice();
        }
        private static void ApplyJam(Toast toast) =>
            Console.WriteLine("Putting jam on the toast");
        private static void ApplyButter(Toast toast) =>
            Console.WriteLine("Putting butter on the toast");
        private static Toast ToastBread(int slices)
        {
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("Putting a slice of bread in the toaster");
            }
            Console.WriteLine("Start toasting...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Remove toast from toaster");
            return new Toast();
        }
        private static Bacon FryBacon(int slices)
        {
            Console.WriteLine($"putting {slices} slices of bacon in the pan");
            Console.WriteLine("cooking first side of bacon...");
            Task.Delay(3000).Wait();
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("flipping a slice of bacon");
            }
            Console.WriteLine("cooking the second side of bacon...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Put bacon on plate");
            return new Bacon();
        }
        private static Egg FryEggs(int howMany)
        {
            Console.WriteLine("Warming the egg pan...");
            Task.Delay(3000).Wait();
            Console.WriteLine($"cracking {howMany} eggs");
            Console.WriteLine("cooking the eggs ...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Put eggs on plate");
            return new Egg();
        }
        private static Coffee PourCoffee()
        {
            Console.WriteLine("Pouring coffee");
            return new Coffee();
        }
    }
}

W powyższym przykładzie wszystkie operacje wykonywane są synchronicznie, czyli po kolei. Nie ma tu ani trochę równoległości. A równoległość jest ważna bo pozwala zaoszczędzić czas i wykorzystać wielordzeniowe procesory.

Jeśli uruchomisz ten kod to zauważysz, że wykonuje się kilkadziesiąt sekund. To trochę długo, bo część operacji możemy przecież wykonywać równolegle. W efekcie zakończenie wszystkich zadań nastąpi kilka razy szybciej.

Kod z użyciem async i await

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ConsoleAppAsyncAwait
{
    public class Coffee { }
    public class Egg {}
    public class Bacon { }
    public class Toast { }
    public class Juice { }
    class Program
    {
        static async Task Main(string[] args)
        {
            Coffee cup = PourCoffee();
            Console.WriteLine("coffee is ready");
            var eggsTask = FryEggsAsync(2);
            var baconTask = FryBaconAsync(3);
            var toastTask = MakeToastWithButterAndJamAsync(2);
            var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
            while (breakfastTasks.Count > 0)
            {
                Task finishedTask = await Task.WhenAny(breakfastTasks);
                if (finishedTask == eggsTask)
                {
                    Console.WriteLine("eggs are ready");
                }
                else if (finishedTask == baconTask)
                {
                    Console.WriteLine("bacon is ready");
                }
                else if (finishedTask == toastTask)
                {
                    Console.WriteLine("toast is ready");
                }
                breakfastTasks.Remove(finishedTask);
            }
            Juice oj = PourOJ();
            Console.WriteLine("oj is ready");
            Console.WriteLine("Breakfast is ready!");
        }
        static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
        {
            var toast = await ToastBreadAsync(number);
            ApplyButter(toast);
            ApplyJam(toast);
            return toast;
        }
        private static Juice PourOJ()
        {
            Console.WriteLine("Pouring orange juice");
            return new Juice();
        }
        private static void ApplyJam(Toast toast) =>
            Console.WriteLine("Putting jam on the toast");
        private static void ApplyButter(Toast toast) =>
            Console.WriteLine("Putting butter on the toast");
        private static async Task<Toast> ToastBreadAsync(int slices)
        {
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("Putting a slice of bread in the toaster");
            }
            Console.WriteLine("Start toasting...");
            await Task.Delay(3000);
            Console.WriteLine("Remove toast from toaster");
            return new Toast();
        }
        private static async Task<Bacon> FryBaconAsync(int slices)
        {
            Console.WriteLine($"putting {slices} slices of bacon in the pan");
            Console.WriteLine("cooking first side of bacon...");
            await Task.Delay(3000);
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("flipping a slice of bacon");
            }
            Console.WriteLine("cooking the second side of bacon...");
            await Task.Delay(3000);
            Console.WriteLine("Put bacon on plate");
            return new Bacon();
        }
        private static async Task<Egg> FryEggsAsync(int howMany)
        {
            Console.WriteLine("Warming the egg pan...");
            await Task.Delay(3000);
            Console.WriteLine($"cracking {howMany} eggs");
            Console.WriteLine("cooking the eggs ...");
            await Task.Delay(3000);
            Console.WriteLine("Put eggs on plate");
            return new Egg();
        }
        private static Coffee PourCoffee()
        {
            Console.WriteLine("Pouring coffee");
            return new Coffee();
        }
    }
}

Kod użyty w przykładach pochodzi ze strony Microsoft: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/

Jeśli chcesz opanować podstawy języka(typy danych, zmienne, pętle, metody, klasy itp. ) C# to koniecznie zajrzyj tutaj

[simple-author-box]