선택된 - c# 트리뷰




왜 내가 '싱글'을 선호해야합니까? 여러 번 넘는 모든 것이 기다리고 있습니까? (4)

(면책 조항 :이 답변은 Ian Griffiths의 Pluralsight TPL 비동기 코스에서 가져온 것입니다.)

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 Task.WhenAll 아닌 모든 작업이 await 있습니까? 예를 들어, DoWord2DoWord2 에 선호되는 방법 아래에 있습니다 (이유는 무엇입니까?).

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

내 이해는 Task.WhenAll 을 선호하는 가장 큰 이유는 성능 / 작업 "churning"을 여러 번 await 있다는 것입니다. DoWork1 메소드는 다음과 같은 작업을 수행합니다.

  • 주어진 context 시작하다
  • 문맥을 저장하다
  • t1을 기다려라.
  • 원래의 맥락을 복원한다.
  • 문맥을 저장하다
  • t2를 기다려라.
  • 원래의 맥락을 복원한다.
  • 문맥을 저장하다
  • t3을 기다려라.
  • 원래의 맥락을 복원한다.

반대로 DoWork2 는 다음과 같은 DoWork2 수행합니다.

  • 주어진 맥락으로 시작하다
  • 문맥을 저장하다
  • t1, t2 및 t3 모두를 기다린다.
  • 원래의 맥락을 복원한다.

이것은 당신의 특별한 경우에 대해 충분히 큰 거래인지 여부는 물론 "상황에 따라 다름"입니다 (장난을 용서하십시오).


예, 모든 오류를 한 번에 전파하므로 WhenAll 사용 WhenAll . 여러 명이 기다리면 이전에 던진 중 하나가 던지기를 기다리면 오류가 발생합니다.

또 다른 중요한 차이점은 WhenAll이 모든 작업이 완료되기를 기다리는 것입니다. await 있는 체인은 첫 번째 예외에서 await 것을 중단하지만 대기 하지 않는 작업의 실행은 계속됩니다. 이로 인해 예기치 않은 동시성이 발생합니다.

나는 또한 당신이 원하는 의미론이 코드에서 직접 문서화되기 때문에 코드를 더 쉽게 읽게한다고 생각한다.


이 질문에 대한 다른 대답은 await 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 t1; await t2; await t3; , 팀원 (또는 어쩌면 당신의 미래 자아!)이 개인 진술을 await 사이에 코드를 추가하는 것을 막을 수있는 것도 아무것도 없습니다. 물론, 그것을 본질적으로 달성하기 위해 한 줄로 압축했는데 문제는 해결되지 않습니다. 게다가 일반적으로 팀 설정에서 주어진 코드 행에 여러 문장을 포함시키는 것은 나쁜 방법입니다. 인간의 눈이 스캔하기가 더 어려워 질 수 있기 때문입니다.

간단히 말하면, await Task.WhenAll(t1, t2, t3); 당신의 의도를보다 명확하게 전달하고, 코드에 대한 의미있는 업데이트로 인해 발생할 수있는 특이한 버그에 덜 취약하거나 심지어 잘못 병합되는 경우에도 관리가 용이합니다.





async-await