.NET에서 Task.WaitAll 대 Task.WhenAll을 사용하는 경우

TPL (Task Parallel Library)은 최신 버전의 .NET 프레임 워크에 추가 된 가장 흥미로운 새 기능 중 하나입니다. Task.WaitAll 및 Task.WhenAll 메서드는 TPL에서 중요하고 자주 사용되는 두 가지 메서드입니다.

Task.WaitAll은 다른 모든 작업이 실행을 완료 할 때까지 현재 스레드를 차단합니다. Task.WhenAll 메서드는 다른 모든 작업이 완료된 경우에만 완료되는 작업을 만드는 데 사용됩니다.

따라서 Task.WhenAll을 사용하는 경우 완료되지 않은 작업 개체를 얻게됩니다. 그러나 차단하지는 않지만 프로그램을 실행할 수 있습니다. 반대로 Task.WaitAll 메서드 호출은 실제로 다른 모든 작업이 완료 될 때까지 차단하고 기다립니다.

기본적으로 Task.WhenAll은 완료되지 않은 작업을 제공하지만 지정된 작업이 실행을 완료하자마자 ContinueWith를 사용할 수 있습니다. Task.WhenAll도 Task.WaitAll도 실제로 작업을 실행하지 않습니다. 즉, 이러한 방법으로 시작되는 작업이 없습니다. 다음은 Task.WhenAll에서 ContinueWith를 사용하는 방법입니다. 

Task.WhenAll (taskList) .ContinueWith (t => {

  // 여기에 코드 작성

});

Microsoft의 설명서에 따르면 Task.WhenAll은 "열거 가능한 컬렉션의 모든 Task 개체가 완료 될 때 완료 될 작업을 만듭니다."

Task.WhenAll 대 Task.WaitAll

이 두 가지 방법의 차이점을 간단한 예를 들어 설명하겠습니다. UI 스레드로 일부 활동을 수행하는 작업이 있다고 가정합니다. 예를 들어 일부 애니메이션은 사용자 인터페이스에 표시되어야합니다. 이제 Task.WaitAll을 사용하면 사용자 인터페이스가 차단되고 모든 관련 작업이 완료되고 차단이 해제 될 때까지 업데이트되지 않습니다. 그러나 동일한 응용 프로그램에서 Task.WhenAll을 사용하는 경우 UI 스레드가 차단되지 않고 평소와 같이 업데이트됩니다.

그렇다면 이러한 방법 중 어떤 것을 사용해야합니까? 인 텐트가 결과를 얻기 위해 동 기적으로 차단 될 때 WaitAll을 사용할 수 있습니다. 그러나 비동기를 활용하려면 WhenAll 변형을 사용하는 것이 좋습니다. 현재 스레드를 차단하지 않고도 Task.WhenAll을 기다릴 수 있습니다. 따라서 비동기 메서드 내에서 Task.WhenAll과 함께 await를 사용할 수 있습니다.

Task.WaitAll은 보류중인 모든 작업이 완료 될 때까지 현재 스레드를 차단하지만 Task.WhenAll은 작업 개체를 반환합니다. Task.WaitAll은 하나 이상의 작업에서 예외가 발생하면 AggregateException을 발생시킵니다. 하나 이상의 작업이 예외를 throw하고 Task.WhenAll 메서드를 기다릴 때 AggregateException을 풀고 첫 번째 예외 만 반환합니다.

Task.Run in 루프를 사용하지 마십시오.

동시 활동을 실행하려는 경우 작업을 사용할 수 있습니다. 높은 수준의 병렬 처리가 필요한 경우 작업은 결코 좋은 선택이 아닙니다. 항상 ASP.Net에서 스레드 풀 스레드를 사용하지 않는 것이 좋습니다. 따라서 ASP.Net에서 Task.Run 또는 Task.factory.StartNew를 사용하지 않아야합니다.

Task.Run은 항상 CPU 바운드 코드에 사용해야합니다. Task.Run은 ASP.Net 응용 프로그램이나 ASP.Net 런타임을 활용하는 응용 프로그램에서는 작업을 ThreadPool 스레드로 오프로드하기 때문에 좋은 선택이 아닙니다. ASP.Net Web API를 사용하는 경우 요청은 이미 ThreadPool 스레드를 사용하고있을 것입니다. 따라서 ASP.Net Web API 응용 프로그램에서 Task.Run을 사용하는 경우 작업을 다른 작업자 스레드로 오프로드하여 확장 성을 제한하는 것입니다.

루프에서 Task.Run을 사용하면 단점이 있습니다. 루프 내에서 Task.Run 메서드를 사용하면 각 작업 단위 또는 반복에 대해 하나씩 여러 작업이 생성됩니다. 그러나 루프 내에서 Task.Run을 사용하는 대신 Parallel.ForEach를 사용하면 필요한 것보다 더 많은 작업을 생성하지 않도록 Partitioner가 생성됩니다. 이렇게하면 너무 많은 컨텍스트 전환을 피하고 시스템에서 여러 코어를 계속 활용할 수 있으므로 성능이 크게 향상 될 수 있습니다.

Parallel.ForEach는 컬렉션을 작업 항목에 배포하기 위해 내부적으로 Partitioner를 사용합니다. 덧붙여서,이 배포는 항목 목록의 각 작업에 대해 발생하는 것이 아니라 일괄 처리로 발생합니다. 이는 관련된 오버 헤드를 줄여 성능을 향상시킵니다. 즉, 루프 내에서 Task.Run 또는 Task.Factory.StartNew를 사용하는 경우 루프의 각 반복에 대해 명시 적으로 새 작업을 만듭니다. Parallel.ForEach는 시스템의 여러 코어에 작업 부하를 분산하여 실행을 최적화하므로 훨씬 더 효율적입니다.