작업 완료 순서에 신경 쓰지 않고 모두 완료해야하는 경우에도 계속 사용해야합니다.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이모든완료 할 작업. 사슬의await
중단하다기다리는첫 번째 예외는 있지만 대기하지 않는 작업의 실행은 계속됩니다. 이로 인해 예기치 않은 동시성이 발생합니다.
나는 또한 당신이 원하는 의미론이 코드에서 직접 문서화되기 때문에 코드를 더 쉽게 읽게한다고 생각한다.
await
그 결과. - svick
내 이해가 그 주된 이유는Task.WhenAll
복수로await
성능 / 작업 "churning"입니다.DoWork1
메서드는 다음과 같이 처리합니다.
대조적으로,DoWork2
이것을한다 :
이것은 당신의 특별한 경우에 대해 충분히 큰 거래인지 여부는 물론 "상황에 따라 달라집니다"(장난을 용서하십시오)입니다.
비동기 메소드는 상태 머신으로 구현됩니다. 상태 머신으로 컴파일되지 않도록 메소드를 작성할 수 있습니다.이 메소드는 고속 트랙 비동기 메소드라고도합니다. 이들은 다음과 같이 구현 될 수 있습니다 :
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);
}
이 질문에 대한 다른 대답은 기술적 이유를 제시합니다.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);
당신의 의도를보다 명확하게 전달하고, 코드에 대한 의미있는 업데이트로 인해 발생할 수있는 특이한 버그에 덜 취약하거나 심지어 잘못 병합되는 경우에도 관리가 용이합니다.
(면책 조항 :이 답변은 Ian Griffiths의 TPL Async 과정에서 가져온 / 영감을 받았습니다.Pluralsight)
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 만 캐치됩니다. 후속 작업 예외는 관찰되지 않을 것입니다.
WhereAll의 경우 - 작업의 일부 또는 전부에 오류가있는 경우 결과 작업에 모든 예외가 포함됩니다. await 키워드는 항상 첫 번째 예외를 항상 다시 throw합니다. 그래서 다른 예외는 여전히 효과적으로 관찰되지 않습니다. 이것을 극복하는 한 가지 방법은 WhenAll 작업 후에 빈 연속을 추가하고 거기에 기다리는 것입니다. 이 방법은 작업이 실패하면 결과 속성이 전체 집계 예외를 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 t1; await t2; ....; await tn
= > 두 번째 경우 모두 항상 최상의 선택입니다. - cuongle