[c#] Task.WhenAll의 연속이 동 기적으로 실행되는 이유는 무엇입니까?

Task.WhenAll.NET Core 3.0에서 실행할 때 방법 에 대해 궁금한 점을 확인했습니다 . 간단한 Task.Delay작업을에 단일 인수로 전달 Task.WhenAll했으며 래핑 된 작업이 원래 작업과 동일하게 작동 할 것으로 예상했습니다. 그러나 이것은 사실이 아닙니다. 원래 작업의 연속은 비동기식으로 (바람직하게) Task.WhenAll(task)실행되고 여러 래퍼 의 연속은 연속적으로 동 기적으로 실행됩니다 (바람직하지 않음).

다음은 이 동작 의 데모 입니다. 4 명의 작업자 작업이 동일한 Task.Delay작업을 완료하기를 기다리고있다 가 무거운 계산을 계속합니다 (로 시뮬레이션 됨 Thread.Sleep).

var task = Task.Delay(500);
var workers = Enumerable.Range(1, 4).Select(async x =>
{
    Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}" +
        $" [{Thread.CurrentThread.ManagedThreadId}] Worker{x} before await");

    await task;
    //await Task.WhenAll(task);

    Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}" +
        $" [{Thread.CurrentThread.ManagedThreadId}] Worker{x} after await");

    Thread.Sleep(1000); // Simulate some heavy CPU-bound computation
}).ToArray();
Task.WaitAll(workers);

출력은 다음과 같습니다. 4 개의 연속이 다른 스레드에서 예상대로 실행됩니다 (병렬).

05:23:25.511 [1] Worker1 before await
05:23:25.542 [1] Worker2 before await
05:23:25.543 [1] Worker3 before await
05:23:25.543 [1] Worker4 before await
05:23:25.610 [4] Worker1 after await
05:23:25.610 [7] Worker2 after await
05:23:25.610 [6] Worker3 after await
05:23:25.610 [5] Worker4 after await

이제 줄 await task을 주석 처리하고 다음 줄의 주석 처리를 제거 await Task.WhenAll(task)하면 출력이 상당히 다릅니다. 모든 연속체가 동일한 스레드에서 실행되므로 계산이 병렬화되지 않습니다. 각 계산은 이전 계산이 완료된 후 시작됩니다.

05:23:46.550 [1] Worker1 before await
05:23:46.575 [1] Worker2 before await
05:23:46.576 [1] Worker3 before await
05:23:46.576 [1] Worker4 before await
05:23:46.645 [4] Worker1 after await
05:23:47.648 [4] Worker2 after await
05:23:48.650 [4] Worker3 after await
05:23:49.651 [4] Worker4 after await

놀랍게도 이것은 각 작업자가 다른 랩퍼를 기다리는 경우에만 발생합니다. 래퍼를 미리 정의하면 :

var task = Task.WhenAll(Task.Delay(500));

… 그리고 await모든 작업자 내에서 동일한 작업을 수행하면 동작은 첫 번째 경우와 동일합니다 (비동기 연속).

내 질문은 : 왜 이런 일이 발생합니까? 동일한 작업의 다른 래퍼의 연속이 동일한 스레드에서 동기식으로 실행되는 원인은 무엇입니까?

참고 :Task.WhenAny 대신 작업을 래핑 Task.WhenAll하면 동일한 이상한 동작이 발생합니다.

또 다른 관찰 : 래퍼를 a 안에 래핑 Task.Run하면 연속성이 비 동기화 될 것으로 예상했습니다 . 그러나 일어나지 않습니다. 아래 줄의 연속은 여전히 ​​동일한 스레드에서 (동기식으로) 실행됩니다.

await Task.Run(async () => await Task.WhenAll(task));

설명 : 위의 차이점은 .NET Core 3.0 플랫폼에서 실행되는 콘솔 응용 프로그램에서 관찰되었습니다. .NET Framework 4.8에서는 원래 작업을 기다리는 것과 작업 래퍼를 ​​기다리는 것 사이에는 차이가 없습니다. 두 경우 모두 연속이 동일한 스레드에서 동기식으로 실행됩니다.



답변

따라서 동일한 작업 변수를 기다리는 여러 비동기 메소드가 있습니다.

    await task;
    // CPU heavy operation

예, 이러한 연속은 task완료되면 연속적으로 호출 됩니다. 귀하의 예에서, 각 연속은 다음 초 동안 스레드를 호그합니다.

각 연속을 비동기 적으로 실행하려면 다음과 같은 것이 필요할 수 있습니다.

    await task;
    await Task.Yield().ConfigureAwait(false);
    // CPU heavy operation

따라서 작업이 초기 연속에서 복귀하고 CPU로드가 외부에서 실행되도록합니다 SynchronizationContext.


답변

을 사용하여 작업을 만들면 작업 Task.Delay()작성 옵션이 None아닌 으로 설정됩니다 RunContinuationsAsychronously.

.net 프레임 워크와 .net 코어 사이의 변경을 방해 할 수 있습니다. 그럼에도 불구하고 관찰하고있는 행동을 설명하는 것으로 보입니다. 또한 소스 코드에 파고에서이를 확인할 수 Task.Delay()있다 최대 newingDelayPromise 기본 호출하는 Task지정되지 생성 옵션을 남기지 않고 생성자를.


답변

귀하의 코드에서 다음 코드는 반복 본문이 아닙니다.

var task = Task.Delay(100);

따라서 다음을 실행할 때마다 작업을 기다리고 별도의 스레드에서 실행합니다.

await task;

그러나 다음을 실행하면의 상태를 확인 task하므로 하나의 스레드에서 실행됩니다.

await Task.WhenAll(task);

그러나 작업 생성을 옆으로 옮기면 WhenAll각 작업이 별도의 스레드에서 실행됩니다.

var task = Task.Delay(100);
await Task.WhenAll(task);


답변