3 가지 작업이 있습니다.
private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}
내 코드를 계속하기 전에 모두 실행해야하며 각 결과도 필요합니다. 어떤 결과도 서로 공통점이 없습니다.
3 가지 작업을 완료 한 다음 결과를 얻으려면 어떻게 전화를 걸어야합니까?
답변
를 사용한 후 다음을 사용 WhenAll
하여 결과를 개별적으로 가져올 수 있습니다 await
.
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
await Task.WhenAll(catTask, houseTask, carTask);
var cat = await catTask;
var house = await houseTask;
var car = await carTask;
당신은 또한 사용할 수 있습니다 Task.Result
(이 시점까지 그들이 모두 성공적으로 완료되었으므로). 그러나 다른 시나리오에서는 문제가 발생할 수 await
있지만 정확하기 때문에 사용하는 것이 좋습니다 Result
.
답변
그냥 await
그들 모두를 시작한 후 개별적으로 세 가지 작업.
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
var cat = await catTask;
var house = await houseTask;
var car = await carTask;
답변
C # 7을 사용하는 경우 다음과 같은 편리한 래퍼 방법을 사용할 수 있습니다.
public static class TaskEx
{
public static async Task<(T1, T2)> WhenAll<T1, T2>(Task<T1> task1, Task<T2> task2)
{
return (await task1, await task2);
}
}
… 반환 유형이 다른 여러 작업을 대기하려는 경우 이와 같은 편리한 구문을 사용합니다. 물론 다른 수의 작업을 기다리려면 여러 개의 오버로드를 수행해야합니다.
var (someInt, someString) = await TaskEx.WhenAll(GetIntAsync(), GetStringAsync());
그러나이 예제를 실제로 바꾸려는 경우 ValueTask 및 이미 완료 된 작업에 대한 일부 최적화에 대해서는 Marc Gravell의 답변을 참조하십시오.
답변
세 가지 작업을 감안할 때 – FeedCat()
, SellHouse()
그리고 BuyCar()
,이 흥미로운 경우가 있습니다 중 그들이 (어떤 이유로, 아마도 캐싱 또는 오류) 모두 완료 기적, 또는 그들이하지 않습니다.
질문에서 우리가 가지고 있다고 가정 해 봅시다.
Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
// what here?
}
이제 간단한 접근 방식은 다음과 같습니다.
Task.WhenAll(x, y, z);
그러나 … 결과 처리에 편리하지 않습니다. 우리는 일반적으로 다음을 원합니다 await
.
async Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
await Task.WhenAll(x, y, z);
// presumably we want to do something with the results...
return DoWhatever(x.Result, y.Result, z.Result);
}
그러나 이것은 많은 오버 헤드를 수행하고 다양한 배열 (배열 포함 params Task[]
)과 목록 (내부)을 할당 합니다. 작동하지만 훌륭한 IMO는 아닙니다. 여러 가지 방법으로 작업 을 사용하는 것이 더 간단 하고 각 async
작업 await
을 차례로 수행 하는 것이 더 간단 합니다 .
async Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
// do something with the results...
return DoWhatever(await x, await y, await z);
}
위의 주석 중 일부와 달리 await
대신 대신 사용 하면 작업이 실행되는 방식 (동시, 순차적 등)과 아무런 차이Task.WhenAll
가 없습니다 . 최상위 수준에서, / 에 대한 우수한 컴파일러 지원 Task.WhenAll
보다 우선하며, 존재하지 않을 때 유용했습니다 . 3 개의 신중한 작업이 아닌 임의의 작업 배열이있는 경우에도 유용합니다.async
await
그러나 우리는 여전히 그 문제가 async
/ await
지속을위한 컴파일러 많은 소음을 발생합니다. 작업 이 실제로 동 기적으로 완료 될 가능성이있는 경우 비동기 폴백을 사용하여 동기 경로를 빌드하여이를 최적화 할 수 있습니다.
Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
if(x.Status == TaskStatus.RanToCompletion &&
y.Status == TaskStatus.RanToCompletion &&
z.Status == TaskStatus.RanToCompletion)
return Task.FromResult(
DoWhatever(a.Result, b.Result, c.Result));
// we can safely access .Result, as they are known
// to be ran-to-completion
return Awaited(x, y, z);
}
async Task Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
return DoWhatever(await x, await y, await z);
}
이 “비동기 폴백을 사용하는 동기화 경로”접근 방식은 특히 동기 완료가 비교적 빈번한 고성능 코드에서 점점 더 일반적입니다. 완료가 항상 비동기 인 경우에는 전혀 도움이되지 않습니다.
여기에 적용되는 추가 사항 :
-
최근 C #에서
async
폴백 방법에 대한 일반적인 패턴 은 일반적으로 로컬 함수로 구현됩니다.Task<string> DoTheThings() { async Task<string> Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) { return DoWhatever(await a, await b, await c); } Task<Cat> x = FeedCat(); Task<House> y = SellHouse(); Task<Tesla> z = BuyCar(); if(x.Status == TaskStatus.RanToCompletion && y.Status == TaskStatus.RanToCompletion && z.Status == TaskStatus.RanToCompletion) return Task.FromResult( DoWhatever(a.Result, b.Result, c.Result)); // we can safely access .Result, as they are known // to be ran-to-completion return Awaited(x, y, z); }
-
선호
ValueTask<T>
에Task<T>
많은 다른 반환 값으로 지금까지 완전히 동 기적으로 사물의 좋은 기회가있는 경우 :ValueTask<string> DoTheThings() { async ValueTask<string> Awaited(ValueTask<Cat> a, Task<House> b, Task<Tesla> c) { return DoWhatever(await a, await b, await c); } ValueTask<Cat> x = FeedCat(); ValueTask<House> y = SellHouse(); ValueTask<Tesla> z = BuyCar(); if(x.IsCompletedSuccessfully && y.IsCompletedSuccessfully && z.IsCompletedSuccessfully) return new ValueTask<string>( DoWhatever(a.Result, b.Result, c.Result)); // we can safely access .Result, as they are known // to be ran-to-completion return Awaited(x, y, z); }
-
가능하면, 선호
IsCompletedSuccessfully
에Status == TaskStatus.RanToCompletion
; 이것은 현재 .NET Core에 존재Task
하며 어디서나 존재합니다.ValueTask<T>
답변
작업에 저장 한 다음 모두 기다릴 수 있습니다.
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
await Task.WhenAll(catTask, houseTask, carTask);
Cat cat = await catTask;
House house = await houseTask;
Car car = await carTask;
답변
모든 오류를 기록하려고하면 코드에 Task.WhenAll 줄을 유지해야합니다. 많은 의견은 코드를 제거하고 개별 작업을 기다릴 수 있다고 제안합니다. Task.WhenAll은 오류 처리에 정말로 중요합니다. 이 줄이 없으면 잠재적으로 관찰되지 않은 예외에 대해 코드를 열어 둡니다.
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
await Task.WhenAll(catTask, houseTask, carTask);
var cat = await catTask;
var house = await houseTask;
var car = await carTask;
다음 코드에서 FeedCat에서 예외가 발생한다고 상상해보십시오.
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
var cat = await catTask;
var house = await houseTask;
var car = await carTask;
이 경우 houseTask 또는 carTask를 기다리지 않습니다. 여기에는 3 가지 가능한 시나리오가 있습니다.
-
FeedCat에 실패하면 SellHouse가 이미 완료되었습니다. 이 경우에는 괜찮습니다.
-
SellHouse가 완료되지 않았으며 어느 시점에서 예외로 실패합니다. 예외는 관찰되지 않으며 종료 자 스레드에서 다시 발생합니다.
-
SellHouse가 완료되지 않았으며 그 안에 들어 있습니다. 코드가 ASP.NET에서 실행되는 경우 대기 중 일부가 코드 내부에서 완료 되 자마자 SellHouse가 실패합니다. FeedCat이 실패하자마자 기본적으로 화재 및 전화 잊기 및 동기화 컨텍스트가 손실 되었기 때문에 발생합니다.
다음은 사례 (3)에 대해 발생하는 오류입니다.
System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. ---> System.NullReferenceException: Object reference not set to an instance of an object.
at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
at System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()
at System.Web.Util.SynchronizationHelper.SafeWrapCallback(Action action)
at System.Threading.Tasks.Task.Execute()
--- End of inner exception stack trace ---
---> (Inner Exception #0) System.NullReferenceException: Object reference not set to an instance of an object.
at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
at System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()
at System.Web.Util.SynchronizationHelper.SafeWrapCallback(Action action)
at System.Threading.Tasks.Task.Execute()<---
사례 (2)의 경우 유사한 오류가 발생하지만 원래 예외 스택 추적이 발생합니다.
.NET 4.0 이상의 경우 TaskScheduler.UnobservedTaskException을 사용하여 관찰되지 않은 예외를 포착 할 수 있습니다. .NET 4.5 이상의 경우 .NET 4.0에 대해 관찰되지 않은 예외가 기본적으로 삼켜집니다. 관찰되지 않은 예외는 프로세스를 중단시킵니다.
자세한 내용 은 .NET 4.5의 작업 예외 처리
답변
스레드 대기 여부에 따라 Task.WhenAll
언급 된대로 또는를 사용할 수 있습니다 Task.WaitAll
. 두 가지에 대한 설명은 링크를 참조하십시오.