TAP(작업 비동기 프로그래밍) 모델 일반적인 비동기 코딩에 대한 추상화 계층을 제공합니다. 이 모델에서는 일반적인 명령문 시퀀스로 코드를 작성합니다. 차이점은 컴파일러가 각 문을 처리하고 다음 문 처리를 시작하기 전에 작업 기반 코드를 읽을 수 있다는 것입니다. 이 모델을 수행하기 위해 컴파일러는 여러 변환을 수행하여 각 작업을 완료합니다. 일부 문은 작업을 시작하고 진행 중인 작업을 나타내는 Task 개체를 반환할 수 있으며 컴파일러는 이러한 변환을 해결해야 합니다. 작업 비동기 프로그래밍의 목표는 문 시퀀스처럼 읽지만 더 복잡한 순서로 실행되는 코드를 사용하도록 설정하는 것입니다. 실행은 외부 리소스 할당 및 태스크가 완료된 시기를 기반으로 합니다.
작업 비동기 프로그래밍 모델은 사람들이 비동기 작업을 포함하는 프로세스에 대한 지침을 제공하는 방법과 유사합니다. 이 문서에서는 일련의 비동기 명령이 포함된 코드에 대해 async
및 await
키워드를 쉽게 추론할 수 있는 방법을 보여 주는 아침 식사를 만드는 지침이 포함된 예제를 사용합니다. 아침 식사를 만들기 위한 지침은 목록으로 제공될 수 있습니다.
- 커피 한 잔을 붓습니다.
- 팬을 가열한 다음 계란 2개를 튀깁니다.
- 베이컨 세 조각을 튀깁니다.
- 빵 두 조각을 토스트합니다.
- 토스트에 버터와 잼을 퍼뜨리세요.
- 오렌지 주스 한 잔을 붓습니다.
요리 경험이 있는 경우 비동기적으로 이러한 지침을 완료할 수 있습니다. 계란을 위해 팬을 달군다. 그런 다음 베이컨을 굽기 시작한다. 빵을 토스터에 넣고 계란을 요리하기 시작합니다. 프로세스의 각 단계에서 작업을 시작한 다음 주의할 준비가 된 다른 작업으로 전환합니다.
아침 식사를 요리하는 것은 병렬이 아닌 비동기 작업의 좋은 예입니다. 한 사람(또는 스레드)이 모든 작업을 처리할 수 있습니다. 한 사람은 이전 작업이 완료되기 전에 다음 작업을 시작하여 아침 식사를 비동기적으로 만들 수 있습니다. 각 요리 작업은 누군가가 프로세스를 적극적으로 보고 있는지 여부에 관계없이 진행됩니다. 계란을 위해 팬을 데우기 시작하자마자 베이컨을 튀길 수 있습니다. 베이컨이 요리하기 시작하면 빵을 토스터에 넣을 수 있습니다.
병렬 알고리즘의 경우 요리하는 여러 사람(또는 여러 스레드)이 필요합니다. 한 사람은 계란을 요리하고, 다른 사람은 베이컨을 튀깁니다. 각 사용자는 하나의 특정 작업에 중점을 둡니다. 요리하는 각 사람(또는 각 스레드)은 현재 작업이 완료될 때까지 동기적으로 차단됩니다. 예를 들어, 베이컨은 뒤집힐 준비가 되어 있고, 빵은 토스터에서 튀어나올 준비가 되어 있습니다.
C# 코드 문으로 작성된 동일한 동기 명령 목록을 고려합니다.
using System;
using System.Threading.Tasks;
namespace AsyncBreakfast
{
// 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 { }
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분이 걸립니다. 기간은 개별 작업 시간의 합계입니다. 컴퓨터는 모든 작업이 완료될 때까지 각 문을 차단한 다음 다음 작업 문으로 진행합니다. 이 방법은 상당한 시간이 걸릴 수 있습니다. 아침 식사 예제에서 컴퓨터 메서드는 만족스럽지 못한 아침 식사를 만듭니다. 동기화 목록의 나중 작업, 예를 들어 빵을 굽는 것은 이전 작업이 완료될 때까지 시작되지 않습니다. 일부 음식은 아침 식사가 제공 될 준비가되기 전에 차가워집니다.
컴퓨터에서 명령을 비동기적으로 실행하려면 비동기 코드를 작성해야 합니다. 클라이언트 프로그램을 작성할 때 UI가 사용자 입력에 응답하도록 합니다. 웹에서 데이터를 다운로드하는 동안 애플리케이션이 모든 상호 작용을 중지해서는 안 됩니다. 서버 프로그램을 작성할 때 다른 요청을 처리할 수 있는 스레드를 차단하지 않으려는 경우 비동기 대안이 있는 경우 동기 코드를 사용하면 비용이 적게 드는 규모 확장 기능이 저하됩니다. 차단된 스레드에 대한 비용을 지불합니다.
성공적인 최신 앱에는 비동기 코드가 필요합니다. 언어 지원이 없으면 비동기 코드를 작성하려면 콜백, 완료 이벤트 또는 코드의 원래 의도를 모호하게 하는 다른 수단이 필요합니다. 동기 코드의 장점은 검색하고 쉽게 이해할 수 있도록 하는 단계별 작업입니다. 기존의 비동기 모델은 코드의 기본 작업이 아니라 코드의 비동기 특성에 집중하도록 합니다.
차단하지 말고 대신 기다립니다.
이전 코드는 비동기 작업을 수행하기 위해 동기 코드를 작성하는 잘못된 프로그래밍 방법을 강조 표시합니다. 이 코드는 현재 스레드가 다른 작업을 수행하지 못하도록 차단합니다. 실행 중인 작업이 있는 동안에는 코드가 스레드를 중단하지 않습니다. 이 모델의 결과는 빵을 넣은 후 토스터를 응시하는 것과 유사합니다. 중단을 무시하고 빵이 팝업될 때까지 다른 작업을 시작하지 않습니다. 냉장고에서 버터와 잼을 꺼내지 않습니다. 난로에서 불이 시작되는 것을 놓칠 수 있습니다. 빵을 토스트하고 다른 문제를 동시에 처리하려고 합니다. 코드도 마찬가지입니다.
태스크가 실행되는 동안 스레드가 차단되지 않도록 코드를 업데이트하여 시작할 수 있습니다.
await
키워드는 작업을 시작한 다음 태스크가 완료되면 실행을 계속할 수 있는 비블로킹 방법을 제공합니다. 간단한 비동기 버전의 아침 식사 코드는 다음 코드 조각과 같습니다.
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!");
}
이 코드는 FryEggs
, FryBacon
, 및 ToastBread
의 원래 메서드 본문을 각각 Task<Egg>
, Task<Bacon>
, 및 Task<Toast>
개체를 반환하도록 업데이트합니다. 업데이트된 메서드 이름에는 "Async" 접미사가 포함됩니다: FryEggsAsync
, FryBaconAsync
, 및 ToastBreadAsync
.
Main
메서드는 Task
개체를 반환하지만, return
식이 없는 것은 설계상 의도된 것입니다. 보다 자세한 내용은 비동기 void 반환 함수 평가에 대한 를 참조하십시오..
메모
업데이트된 코드는 아직 비동기 프로그래밍의 주요 기능을 활용하지 않으므로 완료 시간이 짧아질 수 있습니다. 이 코드는 초기 동기 버전과 거의 동일한 시간 안에 작업을 처리합니다. 전체 메서드 구현은 이 문서의 뒷부분에 코드의 최종 버전을 참조하세요.
업데이트된 코드에 아침 식사 예제를 적용해 보겠습니다. 계란이나 베이컨이 요리하는 동안 스레드는 차단되지 않지만 코드는 현재 작업이 완료될 때까지 다른 작업을 시작하지 않습니다. 당신은 여전히 토스터에 빵을 넣고 빵이 튀어나올 때까지 토스터를 응시하지만, 이제는 방해에 응답할 수 있습니다. 여러 주문이 있는 레스토랑에서는 다른 요리사가 이미 요리하는 동안 요리사가 새로운 주문을 시작할 수 있습니다.
업데이트된 코드에서 아침 식사에서 작업하는 스레드는 완료되지 않은 시작 작업을 기다리는 동안 차단되지 않습니다. 일부 애플리케이션의 경우 이 변경 내용만 있으면 됩니다. 웹에서 데이터를 다운로드하는 동안 앱에서 사용자 상호 작용을 지원하도록 설정할 수 있습니다. 다른 시나리오에서는 이전 작업이 완료될 때까지 기다리는 동안 다른 작업을 시작할 수 있습니다.
동시에 작업 시작
대부분의 작업의 경우 몇 가지 독립적인 작업을 즉시 시작하려고 합니다. 각 작업이 완료되면 시작할 준비가 된 다른 작업을 시작합니다. 이 방법론을 아침 식사 예제에 적용하면 아침 식사를 더 빨리 준비할 수 있습니다. 또한 모든 것을 동시에 준비하여 뜨거운 아침 식사를 즐길 수 있습니다.
System.Threading.Tasks.Task 클래스 및 관련 형식은 진행 중인 작업에 이 추론 스타일을 적용하는 데 사용할 수 있는 클래스입니다. 이 방법을 사용하면 실제 생활에서 아침 식사를 만드는 방법과 더 유사한 코드를 작성할 수 있습니다. 계란, 베이컨, 토스트를 동시에 요리하기 시작합니다. 각 음식 항목에 작업이 필요하므로 해당 작업에 주의를 돌리고, 작업을 처리한 다음, 주의가 필요한 다른 것을 기다립니다.
코드에서 작업을 시작하고 작업을 나타내는 Task 개체를 유지합니다. 작업의 await
메서드를 사용하여 결과가 준비될 때까지 작업 작업을 지연합니다.
이러한 변경 내용을 아침 식사 코드에 적용합니다. 첫 번째 단계는 await
식을 사용하는 대신, 작업이 시작될 때 운영을 위한 작업을 저장하는 것입니다.
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
식은 시작하는 즉시 모든 작업에 적용됩니다. 다음 단계는 베이컨과 계란에 대한 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분이 걸리는 비동기식으로 준비된 아침 식사가 있습니다. 일부 작업이 동시에 실행되므로 총 조리 시간이 줄어듭니다.
코드 업데이트는 요리 시간을 줄여 준비 프로세스를 개선하지만 계란과 베이컨을 태워 회귀를 도입합니다. 모든 비동기 작업을 한 번에 시작합니다. 결과가 필요한 경우에만 각 작업을 기다립니다. 코드는 다른 마이크로 서비스에 대한 요청을 한 다음 결과를 단일 페이지로 결합하는 웹 애플리케이션의 프로그램과 유사할 수 있습니다. 모든 요청을 즉시 수행하고 모든 작업에 await
식을 적용하고 웹 페이지를 작성합니다.
작업으로 구성 지원
이전 코드 수정은 토스트를 제외한 모든 아침 식사가 동시에 준비되도록 돕습니다. 토스트를 만드는 과정은 빵을 토스트하는 비동기 작업과 토스트에 버터와 잼을 바르는 동기 작업의 및 구성입니다. 이 예제에서는 비동기 프로그래밍에 대한 중요한 개념을 보여 줍니다.
중요하다
비동기 작업에 이어 동기 작업이 이루어진 구성은 비동기 작업입니다. 작업의 일부가 비동기인 경우 전체 작업이 비동기인 다른 방법을 설명합니다.
이전 업데이트에서는 Task 또는 Task<TResult> 개체를 사용하여 실행 중인 작업을 보유하는 방법을 알아보았습니다. 각 작업의 결과를 사용하기 전에 기다립니다. 다음 단계는 다른 작업의 조합을 나타내는 메서드를 만드는 것입니다. 아침 식사를 제공하기 전에, 버터와 잼을 바르기 전에 빵을 먼저 토스트하는 작업을 기다려야 합니다.
다음 코드를 사용하여 이 작업을 나타낼 수 있습니다.
static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
{
var toast = await ToastBreadAsync(number);
ApplyButter(toast);
ApplyJam(toast);
return toast;
}
MakeToastWithButterAndJamAsync
메서드에는 해당 메서드가 async
식을 포함하고 비동기 작업을 포함한다는 신호를 컴파일러에 알리는 서명에 await
한정자가 있습니다. 이 메서드는 빵을 토스트한 다음 버터와 잼을 퍼뜨리는 작업을 나타냅니다. 이 메서드는 세 연산의 컴퍼지션을 나타내는 Task<TResult> 개체를 반환합니다.
이제 수정된 주 코드 블록은 다음과 같습니다.
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!");
}
이 코드 변경은 비동기 코드를 사용하기 위한 중요한 기술을 보여 줍니다. 작업을 작업을 반환하는 새 메서드로 구분하여 작업을 작성합니다. 해당 작업을 대기할 시기를 선택할 수 있습니다. 다른 작업을 동시에 시작할 수 있습니다.
비동기 예외 처리
이 시점까지 코드는 모든 작업이 성공적으로 완료된 것으로 암시적으로 가정합니다. 비동기 메서드는 동기 메서드와 마찬가지로 예외를 throw합니다. 예외 및 오류 처리에 대한 비동기 지원의 목표는 일반적으로 비동기 지원과 동일합니다. 모범 사례는 일련의 동기 문처럼 읽는 코드를 작성하는 것입니다. 태스크는 성공적으로 완료되지 않으면 예외를 발생시킵니다. 클라이언트 코드는 await
식이 시작된 작업에 적용될 때 이러한 예외를 catch할 수 있습니다.
아침 식사 예제에서 토스터가 빵을 토스트하는 동안 불을 잡는다고 가정합니다. 다음 코드와 일치하도록 ToastBreadAsync
메서드를 수정하여 해당 문제를 시뮬레이션할 수 있습니다.
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(2000);
Console.WriteLine("Fire! Toast is ruined!");
throw new InvalidOperationException("The toaster is on fire");
await Task.Delay(1000);
Console.WriteLine("Remove toast from toaster");
return new Toast();
}
메모
이 코드를 컴파일하면 연결할 수 없는 코드에 대한 경고가 표시됩니다. 이 오류는 의도적으로 수행됩니다. 토스터가 화재를 일으킨 후에는 작업이 정상적으로 진행되지 않으며 코드에서 오류를 반환합니다.
코드를 변경한 후 애플리케이션을 실행하고 출력을 확인합니다.
Pouring coffee
Coffee is ready
Warming the egg pan...
putting 3 slices of bacon in the pan
Cooking first side of bacon...
Putting a slice of bread in the toaster
Putting a slice of bread in the toaster
Start toasting...
Fire! Toast is ruined!
Flipping a slice of bacon
Flipping a slice of bacon
Flipping a slice of bacon
Cooking the second side of bacon...
Cracking 2 eggs
Cooking the eggs ...
Put bacon on plate
Put eggs on plate
Eggs are ready
Bacon is ready
Unhandled exception. System.InvalidOperationException: The toaster is on fire
at AsyncBreakfast.Program.ToastBreadAsync(Int32 slices) in Program.cs:line 65
at AsyncBreakfast.Program.MakeToastWithButterAndJamAsync(Int32 number) in Program.cs:line 36
at AsyncBreakfast.Program.Main(String[] args) in Program.cs:line 24
at AsyncBreakfast.Program.<Main>(String[] args)
토스터에 불이 붙고 시스템에서 예외를 감지하는 사이에 꽤 많은 작업이 완료됩니다. 비동기적으로 실행되는 작업이 예외를 throw하면 해당 태스크에 오류가 .
Task
개체는 Task.Exception 속성에 throw된 예외를 보유합니다. 오류가 발생한 작업은 await
식이 작업에 적용될 때 예외를 발생시킵니다.
이 프로세스에 대해 이해해야 하는 두 가지 중요한 메커니즘이 있습니다.
- 오류 작업에 예외가 저장되는 방법
- 오류가 발생한 태스크에서 코드가 대기할 때 예외가 패키지 해제되고 다시 throw되는 방법(
await
)
실행 중인 코드가 비동기적으로 예외를 throw하면 예외가 Task
개체에 저장됩니다. 비동기 작업 중에 둘 이상의 예외가 throw될 수 있으므로 Task.Exception 속성은 System.AggregateException 개체입니다. 발생된 예외는 AggregateException.InnerExceptions 컬렉션에 추가됩니다.
Exception
속성이 null이면 새 AggregateException
개체가 만들어지고 throw된 예외가 컬렉션의 첫 번째 항목입니다.
오류 작업에 대한 가장 일반적인 시나리오는 Exception
속성에 정확히 하나의 예외가 포함되어 있다는 것입니다. 코드에서 오류가 발생한 작업을 기다리면 컬렉션의 첫 번째 AggregateException.InnerExceptions 예외가 다시 발생합니다. 이 결과는 예제의 출력이 System.InvalidOperationException 개체가 아닌 AggregateException
개체를 표시하는 이유입니다. 첫 번째 내부 예외를 추출하면 비동기 메서드를 사용하여 동기 메서드를 사용하는 것과 최대한 유사하게 작업할 수 있습니다. 시나리오에서 여러 예외를 생성할 수 있는 경우 코드에서 Exception
속성을 검사할 수 있습니다.
다음 섹션으로 계속 진행하기 전에 ToastBreadAsync
메서드에서 다음 두 문을 주석으로 처리합니다. 다른 화재를 시작하고 싶지 않습니다.
Console.WriteLine("Fire! Toast is ruined!");
throw new InvalidOperationException("The toaster is on fire");
await 식을 작업에 효율적으로 적용하세요.
await
클래스의 메서드를 사용하여 이전 코드의 끝에 있는 일련의 Task
식을 개선할 수 있습니다. 하나의 API는 인수 목록의 모든 작업이 완료되면 완료되는 WhenAll 개체를 반환하는 Task 메서드입니다. 다음 코드는 이 메서드를 보여 줍니다.
await Task.WhenAll(eggsTask, baconTask, toastTask);
Console.WriteLine("Eggs are ready");
Console.WriteLine("Bacon is ready");
Console.WriteLine("Toast is ready");
Console.WriteLine("Breakfast is ready!");
또 다른 옵션은 인수가 완료되면 완료되는 WhenAny 개체를 반환하는 Task<Task>
메서드를 사용하는 것입니다. 작업이 완료된 것을 알고 있으므로 반환된 작업을 기다릴 수 있습니다. 다음 코드에서는 WhenAny 메서드를 사용하여 첫 번째 작업이 완료될 때까지 기다린 다음 결과를 처리하는 방법을 보여 있습니다. 완료된 작업의 결과를 처리한 후 WhenAny
메서드에 전달된 작업 목록에서 완료된 작업을 제거합니다.
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");
}
await finishedTask;
breakfastTasks.Remove(finishedTask);
}
코드 조각의 끝부분에 await finishedTask;
식을 확인합니다.
await Task.WhenAny
식은 완료된 작업을 기다리지 않고 Task
메서드에서 반환된 Task.WhenAny
개체를 기다립니다.
Task.WhenAny
메서드의 결과는 완료된(또는 오류가 있는) 작업입니다. 작업이 완료된 것을 알고 있는 경우에도 작업을 다시 기다리는 것이 가장 좋습니다. 이러한 방식으로 작업 결과를 가져오거나, 작업에 오류를 발생시키는 예외가 발생했는지 확인할 수 있습니다.
최종 코드 검토
코드의 최종 버전은 다음과 같습니다.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace AsyncBreakfast
{
// 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 { }
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");
}
await finishedTask;
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();
}
}
}
코드는 약 15분 안에 비동기 아침 식사 작업을 완료합니다. 일부 작업이 동시에 실행되므로 총 시간이 줄어듭니다. 코드는 동시에 여러 작업을 모니터링하고 필요에 따라 작업을 수행합니다.
최종 코드는 비동기입니다. 그것은 더 정확하게 사람이 아침 식사를 요리 할 수있는 방법을 반영합니다. 최종 코드를 문서의 첫 번째 코드 샘플과 비교합니다. 핵심 작업은 코드를 읽어도 여전히 명확합니다. 문서의 시작 부분에 표시된 것처럼 아침 식사를 만들기 위한 지침 목록을 읽는 것과 동일한 방식으로 최종 코드를 읽을 수 있습니다.
async
및 await
키워드의 언어 기능은 작성된 지침에 따라 번역할 수 있도록 안내합니다. 가능한 한 작업을 시작하고, 작업이 완료되길 기다리면서 차단되지 않도록 하세요.
다음 단계
비동기 프로그램 대한 실제 시나리오 살펴보기
.NET