[C# 문법] async 및 await 를 사용한 비동기 프로그래밍

참고


개요

  • TAP(Task 비동기 프로그래밍) 모델은 비동기 코드에 대한 추상화를 제공하고 있습니다.
  • 항상 그렇듯이 코드는 일련의 명령문으로 작성합니다.
  • 다음 명령문이 시작되기 전에 각 명령문이 완료되는 것처럼 해당 코드를 읽을 수 있습니다.
  • 이러한 명령문 중 일부에서 작업을 시작하고 진행 중인 작업을 나타내는 Task 를 반환할 수 있으므로 컴파일러는 여러 가지 변환을 수행합니다.

예제 시나리오

  • 이 문서에서는 아침 식사를 만드는 지침의 예를 사용하여 일련의 비동기 명령이 포함된 코드에 대해 더 쉽게 추론할 수 있는 방법 async 및 await 키워드를 확인합니다.
  • 아침 식사를 준비하는 방법을 설명하기 ㅜ이해 작성하는 지침은 다음 목록과 같습니다.
    • 커피 한 잔을 따릅니다.
    • 팬을 가열한 다음 달걀 두 개를 튀깁니다.
    • 베이컨 세 조각을 튀깁니다.
    • 빵 두 조각을 굽습니다.
    • 토스트에 버터와 잼을 바릅니다.
    • 오렌지 주스 한자을 따릅니다.
  • 위 시나리오를 수행할 떄, 요리에 경험이 있는 경우 이러한 지침은 비동기적으로 실행됩니다.
  • 계란 프라이를 위해 팬을 데우기 시작한 다음, 베이컨을 시작합니다.
  • 토스터에 빵을 넣고 계한 프라이를 시작합니다.
  • 프로세스의 각 단계에서 작업을 시작한 다음, 주의가 필요한 작업에 주의를 돌립니다.
  • 아침을 요리하는 것은 병렬로 수행되지 않는 비동기 작업의 좋은 예입니다.
  • 한 사람이 이러한 모든 작업을 처리할 수 있습니다.
  • 아침 식사 비유를 계속하면 한 사람이 첫 번쨰 작업이 완료되기 전에 다음 작업을 시작하여 비동기적으로 아침 식사를 할 수 있습니다.
  • 누군가 보고 있는지 여부에 관계없이 요리는 계속 됩니다.
  • 계란 프라이를 위해 팬을 데우기 시작하자 마자 베이컨을 튀기기 시작할 수 있습니다.
  • 베이컨 튀김이 시작되면 토스터에 빵을 넣을 수 있습니다.

  • 병렬 알고리즘의 경우 여러 요리가(쓰레드) 가 필요합니다.

  • 한 사람은 계란을 만들고 또 한 사람은 베이컨을 만드는 방식으로 진행됩니다.

  • 즉 각각은 하나의 작업에만 집중할 것입니다.

  • 각 요리사(쓰레드)는 베이컨이 뒤집을 준비가 될 때까지 동기적으로 차단되거나 토스트가 튀어 오를 때까지 차단됩니다.

  • 위 내용을 C# 코드로 표현하면 다음과 같습니다.

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

  • 동기적으로 준비된 아침 식사는 총합이 각 작업의 합계이기 떄문에 약 30분이 걸렸습니다.
  • 그럼 위 내용을 비동기 적으로 실행하게 하기 위하여 비동기 코드로 작성해 보도록 하겠습니다.

차단하는 대신 대기

  • 앞의 코드에서는 동기 코드를 구성하여 비동기 작업을 수행하는 잘못된 사례를 보여 줍니다.
  • 작성한 대로 이 코드는 실행되는 스레드에서 다른 작업을 수행하지 못하도록 차단합니다.
  • 작업이 진행되는 동안에는 중단되지 않습니다.
  • 마치 빵을 넣은 후 토스터를 쳐다보는 것과 같습니다.
  • 토스트가 나오기 전까지 아무하고도 대화하지 않을 것입니다.
  • 먼저 이 코드를 업데이트하여 작업이 실행되는 동안 스레드가 차단되지 않도록 하겠습니다.
  • await 키워드는 작업을 차단하지 않는 방식으로 시작한 다음, 해당 작업이 완료되면 실행을 계속합니다. 간단한 비동기 버전의 아침 식사 준비 코드는 다음과 같습니다.
// These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.
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!");
}

총 경과 시간은 초기 동기 버전과 거의 같습니다. 이 코드에서는 아직 비동기 프로그래밍의 몇 가지 주요 기능을 활용하지 않았습니다.

FryEggsAsync, FryBaconAsync 및 ToastBreadAsync의 메서드 본문은 각각 Task, Task 및 Task를 반환하도록 모두 업데이트되었습니다. 이 메서드들은 원래 버전에서 “Async” 접미사를 포함하도록 이름이 바뀌었습니다. 해당 구현은 이 문서의 뒷부분에 나오는 최종 버전의 일부로 표시됩니다.

  • 이 코드는 계란이나 베이컨을 요리하는 동안 차단되지 않습니다.
  • 하지만 이 코드는 다른 작업을 시작하지 않습니다.
  • 토스트가 토스터에 넣어져 나올 때까지 쳐다보고 있습니다.
  • 그러나 적어도 주의를 끌려고 하는 누구에게나 응답할 수는 있습니다.
  • 여러 주문을 받는 식당에서 요리사는 첫 번째 요리를 하는 동안 또 다른 아침 식사 준비를 시작할 수 있습니다.

동시에 작업 시작

  • 대부분의 시나리오에서는 독립적인 몇 가지 작업을 즉시 시작하려고 합니다.
  • 그런 다음, 각 작업이 완료되면 준비된 다른 작업을 계속할 수 있습니다.
  • 아침 식사 비유에서 이는 아침 식사를 더 빨리 준비하는 방법입니다.
  • 모든 것을 거의 동시에 완료할 수 있습니다.
  • 이에 따라 따뜻한 아침 식사가 준비됩니다.
  • 코드를 다음과 같이 변경해 보도록 하겠습니다.
  • 첫 번째 단계는 작업을 기다리지 않고 시작될 때 해당 작업을 저장하는 것입니다.
Coffee cup = PourCoffee();
Console.WriteLine("Coffee is ready");

Task<Egg> eggsTask = FryEggsAsync(2);
Egg eggs = await eggsTask;
Console.WriteLine("Eggs are ready");

Task<Bacon> baconTask = FryBaconAsync(3);
Bacon bacon = await baconTask;
Console.WriteLine("Bacon is ready");

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");
Console.WriteLine("Breakfast is ready!");
  • 다음으로, 아침 식사를 제공하기 전에 베이컨과 달걀에 대한 await 문을 메서드 끝으로 이동할 수 있습니다.
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!");

  • 비동기적으로 준비된 아침 식사에는 대략 20분이 걸렸는데, 일부 작업이 동시에 실행 되었기 때문에 이렇게 시간을 절약할 수 있는 것입니다.
728x90

이 글을 공유하기

댓글

Designed by JB FACTORY