작업 완료 순서에 신경 쓰지 않고 모두 완료해야하는 경우에도 await Task.WhenAll
여러 개 대신 사용해야 await
합니까? 예를 들어 DoWork2
다음과 같은 선호하는 방법 이 있습니다 DoWork1
.
using System;
using System.Threading.Tasks;
namespace ConsoleApp
{
class Program
{
static async Task<string> DoTaskAsync(string name, int timeout)
{
var start = DateTime.Now;
Console.WriteLine("Enter {0}, {1}", name, timeout);
await Task.Delay(timeout);
Console.WriteLine("Exit {0}, {1}", name, (DateTime.Now - start).TotalMilliseconds);
return name;
}
static async Task DoWork1()
{
var t1 = DoTaskAsync("t1.1", 3000);
var t2 = DoTaskAsync("t1.2", 2000);
var t3 = DoTaskAsync("t1.3", 1000);
await t1; await t2; await t3;
Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
}
static async Task DoWork2()
{
var t1 = DoTaskAsync("t2.1", 3000);
var t2 = DoTaskAsync("t2.2", 2000);
var t3 = DoTaskAsync("t2.3", 1000);
await Task.WhenAll(t1, t2, t3);
Console.WriteLine("DoWork2 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
}
static void Main(string[] args)
{
Task.WhenAll(DoWork1(), DoWork2()).Wait();
}
}
}
답변
예, WhenAll
모든 오류를 한 번에 전파하므로 사용 하십시오. 다중 대기를 사용하면 이전 대기 중 하나가 발생하면 오류가 손실됩니다.
또 다른 중요한 차이점은 실패 (오류 또는 취소 된 작업)가있는 경우에도WhenAll
모든 작업이 완료 될 때까지 기다린다 는 것 입니다 . 수동으로 순서대로 기다리면 기다리려는 프로그램 부분이 실제로 일찍 계속되기 때문에 예기치 않은 동시성이 발생합니다.
또한 원하는 의미가 코드에 직접 문서화되어 있기 때문에 코드를 더 쉽게 읽을 수 있다고 생각합니다.
답변
내 이해는 Task.WhenAll
여러 await
s 를 선호하는 주된 이유 는 성능 / 작업 “이탈”입니다.이 DoWork1
방법은 다음과 같은 작업을 수행합니다.
- 주어진 문맥으로 시작
- 맥락을 저장
- t1을 기다리다
- 원래 컨텍스트 복원
- 맥락을 저장
- t2를 기다리다
- 원래 컨텍스트 복원
- 맥락을 저장
- t3를 기다리다
- 원래 컨텍스트 복원
대조적으로 다음을 DoWork2
수행합니다.
- 주어진 문맥으로 시작
- 맥락을 저장
- t1, t2 및 t3 모두를 기다립니다.
- 원래 컨텍스트 복원
이것이 귀하의 특정 사건에 대해 충분히 큰 거래인지 여부는 물론 “문맥 의존적”입니다 (말장난을 용서하십시오).
답변
비동기 메서드는 상태 머신으로 구현됩니다. 상태 머신으로 컴파일되지 않도록 메서드를 작성할 수 있습니다.이를 종종 빠른 트랙 비동기 메서드라고합니다. 다음과 같이 구현할 수 있습니다.
public Task DoSomethingAsync()
{
return DoSomethingElseAsync();
}
사용할 때 Task.WhenAll
호출자가 모든 작업이 완료 될 때까지 기다릴 수 있도록하면서이 빠른 추적 코드를 유지하는 것이 가능합니다. 예 :
public Task DoSomethingAsync()
{
var t1 = DoTaskAsync("t2.1", 3000);
var t2 = DoTaskAsync("t2.2", 2000);
var t3 = DoTaskAsync("t2.3", 1000);
return Task.WhenAll(t1, t2, t3);
}
답변
(면책 조항 :이 답변은 Pluralsight 에 대한 Ian Griffiths의 TPL Async 과정에서 가져 왔습니다. )
WhenAll을 선호하는 또 다른 이유는 예외 처리입니다.
DoWork 메서드에 try-catch 블록이 있고 다른 DoTask 메서드를 호출한다고 가정합니다.
static async Task DoWork1() // modified with try-catch
{
try
{
var t1 = DoTask1Async("t1.1", 3000);
var t2 = DoTask2Async("t1.2", 2000);
var t3 = DoTask3Async("t1.3", 1000);
await t1; await t2; await t3;
Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
}
catch (Exception x)
{
// ...
}
}
이 경우 3 개 작업 모두에서 예외가 발생하면 첫 번째 작업 만 잡 힙니다. 이후의 모든 예외는 손실됩니다. 즉, t2와 t3이 예외를 던지면 t2 만 잡 힙니다. 등. 후속 작업 예외는 관찰되지 않습니다.
WhenAll에서와 같이-작업의 일부 또는 전부에 오류가있는 경우 결과 작업에 모든 예외가 포함됩니다. await 키워드는 여전히 항상 첫 번째 예외를 다시 발생시킵니다. 따라서 다른 예외는 여전히 효과적으로 관찰되지 않습니다. 이것을 극복하는 한 가지 방법은 WhenAll 작업 뒤에 빈 연속을 추가하고 거기에 await를 두는 것입니다. 이렇게하면 작업이 실패하면 결과 속성이 전체 집계 예외를 throw합니다.
static async Task DoWork2() //modified to catch all exceptions
{
try
{
var t1 = DoTask1Async("t1.1", 3000);
var t2 = DoTask2Async("t1.2", 2000);
var t3 = DoTask3Async("t1.3", 1000);
var t = Task.WhenAll(t1, t2, t3);
await t.ContinueWith(x => { });
Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t.Result[0], t.Result[1], t.Result[2]));
}
catch (Exception x)
{
// ...
}
}
답변
이 질문에 대한 다른 답변은 기술적 인 이유를 제공합니다. await Task.WhenAll(t1, t2, t3);
은 선호 . 이 대답은 @usr이 암시하는 더 부드러운 측면에서 보는 것을 목표로하지만 여전히 동일한 결론에 도달합니다.
await Task.WhenAll(t1, t2, t3);
의도를 선언하고 원자 적이므로보다 기능적인 접근 방식입니다.
를 사용하면 await t1; await t2; await t3;
팀원 (또는 미래의 자신)이 개인간에 코드를 추가하는 것을 막을 수 없습니다.await
명령문 . 물론 기본적으로이를 달성하기 위해 한 줄로 압축했지만 문제가 해결되지는 않습니다. 게다가, 팀 설정에서 주어진 코드 줄에 여러 문장을 포함하는 것은 일반적으로 나쁜 형식입니다. 이는 사람의 눈으로 소스 파일을 스캔하기 어렵게 만들 수 있기 때문입니다.
간단히 말해, await Task.WhenAll(t1, t2, t3);
의도를 더 명확하게 전달하고 코드에 대한 의미있는 업데이트에서 나올 수있는 특이한 버그에 덜 취약하거나 심지어 병합이 잘못되어 유지되기 때문에 유지 관리가 더 쉽습니다.