async와 await 키워드

동기와 비동기

  • 동기
    • 어떤 operation을 실행시켰을 때 결과값이 도출될 때까지 프로그램은 멈춰서 기다려야 함
  • 비동기
    • 언제 값이 도출될 지 보장할 수 없고, 그 동안 프로그램은 다른 프로세스를 진행할 수 있음
  • async / await는 위 중 비동기 프로그래밍을 위해 사용될 키워드

아침 준비하기

using System;
using System.Threading.Tasks;

namespace AsyncBreakfast
{
    // 아래 클래스는 예시를 위한 더미 클래스
    internal class Bacon { }
    internal class Coffee { }
    internal class Egg { }
    internal class Juice { }
    internal class Toast { }

    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();
        }
    }
}

post_thumbnail

  • 위의 코드대로라면, 아침 식사를 동기적으로 수행했고 총 30붕이 소요됐다
  • 하나의 작업이 완료되기 전까지는 다른 작업을 실행할 수 없다
    • 위의 작업을 비동기로 실행하려면, 코드를 비동기로 작성해야 한다
  • 위의 코드는, 실행되는 스레드에서 다른 스레드의 작업을 수행하지 못하도록 차단해버린다
    • 작업이 진행되는 동안에는 중단되지 않는다

차단하는 대신 대기하기

internal class Bacon { }
internal class Coffee { }
internal class Egg { }
internal class Juice { }
internal class Toast { }

static async Task Main(string[] args)
{
    Coffee cup = PourCoffee();
    Console.WriteLine("coffee is ready");

    Egg eggs = await FryEggsAsync(2);
    Console.WriteLine("eggs are ready");

    Bacon bacon = await FryBaconAsync(3);
    Console.WriteLine("bacon is ready");

    Toast toast = await ToastBreadAsync(2);
    ApplyButter(toast);
    ApplyJam(toast);
    Console.WriteLine("toast is ready");

    Juice oj = PourOJ();
    Console.WriteLine("oj is ready");
    Console.WriteLine("Breakfast is ready!");
}
  • 위 코드도 소요시간은 동기 코드와 거의 동일하다
  • 그러나 위 코드에서는, 적어도 한 스레드가 다른 스레드의 작업을 강제로 차단하지는 않는다
    • 계란 프라이를 하면서 빵이 다 구워졌나 쳐다보는 것 정도는 가능하다

동시에 시작하기

Coffee cup = PourCoffee();
Console.WriteLine("Coffee is ready");

Task<Egg> eggsTask = FryEggsAsync(2);
Task<Bacon> baconTask = FryBaconAsync(3);
Task<Toast> toastTask = ToastBreadAsync(2);

Toast toast = await toastTask;
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");
Juice oj = PourOJ();
Console.WriteLine("Oj is ready");

Egg eggs = await eggsTask;
Console.WriteLine("Eggs are ready");
Bacon bacon = await baconTask;
Console.WriteLine("Bacon is ready");

Console.WriteLine("Breakfast is ready!");

post_thumbnail

  • 기존 작업이 완료되기까지 기다리지 않고 다음 작업을 시작할 때, 진행하던 작업을 저장할 수 있다
    • Task 키워드를 사용한다
  • 위의 코드는 20여분이 소요됐다
    • 일부 작업이 동시에 실행되었기 때문
  • 또한, 위의 코드가 하나의 메서드 일때, 메서드는 비동기로 실행하기 위한 async 키워드를 붙여야 한다
internal class Bacon { }
internal class Coffee { }
internal class Egg { }
internal class Juice { }
internal class Toast { }

static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
{
    var toast = await ToastBreadAsync(number);
    ApplyButter(toast);
    ApplyJam(toast);

    return toast;
}

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 eggs = await eggsTask;
    Console.WriteLine("eggs are ready");

    var bacon = await baconTask;
    Console.WriteLine("bacon is ready");

    var toast = await toastTask;
    Console.WriteLine("toast is ready");

    Juice oj = PourOJ();
    Console.WriteLine("oj is ready");
    Console.WriteLine("Breakfast is ready!");
}

async 메서드

  • async는 await 키워드를 메서드 내에서 사용할 수 있게 해준다
  • void, Task, 혹은 Task를 반환해야 한다
  • void를 반환하면 메서드를 호출하는 쪽에서 비동기 제어를 할 수 없다
    • UI 버튼을 클릭하면 일어나는 작업들을 비동기로 처리할 때 void를 사용한다고 한다

await

  • void도 반환하지만 Task 또는 Task 역시 반환한다

출처

Categories: ,

Updated: