Parallel.ForEach 대 Task.Run 및 Task.WhenAll


162

Parallel.ForEach 또는 Task.Run ()을 사용하여 작업 집합을 비동기 적으로 시작하는 것의 차이점은 무엇입니까?

버전 1 :

List<string> strings = new List<string> { "s1", "s2", "s3" };
Parallel.ForEach(strings, s =>
{
    DoSomething(s);
});

버전 2 :

List<string> strings = new List<string> { "s1", "s2", "s3" };
List<Task> Tasks = new List<Task>();
foreach (var s in strings)
{
    Tasks.Add(Task.Run(() => DoSomething(s)));
}
await Task.WhenAll(Tasks);

4
Task.WaitAll대신에 사용하면 두 번째 코드 조각이 첫 번째 코드 조각과 거의 같을 것이라고 생각합니다 Task.WhenAll.
avo

15
두 번째 작업은 DoSomething ( "s3")을 세 번 수행하고 동일한 결과를 생성하지 않습니다. stackoverflow.com/questions/4684320/…
Nullius


@Dan : 버전 2는 async / await를 사용하므로 다른 질문입니다. Async / await는 가능한 중복 스레드가 작성된 지 1.5 년 후 VS 2012에서 도입되었습니다.
Petter T

답변:


163

이 경우 두 번째 메서드는 블로킹 대신 작업이 완료 될 때까지 비동기 적으로 대기합니다.

그러나 Task.Run루프에서 사용 하는 데는 단점이 있습니다 . With Parallel.ForEach, Partitioner필요한 것보다 더 많은 작업을 만들지 않도록 생성되는이 있습니다. Task.Run이 작업을 수행하기 때문에 항상 항목 당 단일 작업을 만들지 만 Parallel클래스 일괄 처리가 작동하므로 전체 작업 항목보다 적은 작업을 만들 수 있습니다. 특히 루프 본문에 항목 당 작업량이 적은 경우 전체 성능이 크게 향상 될 수 있습니다.

이 경우 다음과 같이 작성하여 두 옵션을 결합 할 수 있습니다.

await Task.Run(() => Parallel.ForEach(strings, s =>
{
    DoSomething(s);
}));

다음과 같은 짧은 형식으로도 작성할 수 있습니다.

await Task.Run(() => Parallel.ForEach(strings, DoSomething));

1
좋은 대답입니다.이 주제에 대한 좋은 읽기 자료를 알려줄 수 있는지 궁금합니다.
Dimitar Dimitrov

@DimitarDimitrov 일반적인 TPL 관련 내용은 reedcopsey.com/series/parallelism-in-net4
Reed Copsey

1
내 Parallel.ForEach 구문이 내 응용 프로그램을 중단했습니다. 내부에서 무거운 이미지 처리를 수행하고있었습니다. 그러나 Task.Run (() => Parallel.ForEach (....)); 충돌이 멈췄습니다. 이유를 설명해 주시겠습니까? 병렬 옵션을 시스템의 코어 수로 제한합니다.
monkeyjumps 2014

3
어떤 경우 DoSomething이다 async void DoSomething?
Francesco Bonizzi

1
어때 async Task DoSomething?
Shawn Mclean 17

36

첫 번째 버전은 호출 스레드를 동 기적으로 차단하고 일부 작업을 실행합니다.
UI 스레드 인 경우 UI가 고정됩니다.

두 번째 버전은 스레드 풀에서 비동기 적으로 작업을 실행하고 완료 될 때까지 호출 스레드를 해제합니다.

사용되는 스케줄링 알고리즘에도 차이가 있습니다.

두 번째 예는 다음과 같이 줄일 수 있습니다.

await Task.WhenAll(strings.Select(s => Task.Run(() => DoSomething(s)));

2
await Task.WhenAll(strings.Select(async s => await Task.Run(() => DoSomething(s)));그래? 나는 (기다리는 대신) 작업을 반환 할 때 문제가 있었다. 특히 using객체를 처리하기 위해 같은 명령문 이 포함 되었을 때 .
Martín Coll

내 Parallel.ForEach 호출로 인해 UI가 중단되었습니다 Task.Run (() => Parallel.ForEach (....)); 그것에 충돌을 해결했습니다.
monkeyjumps 2014

1

읽기가 더 쉽다고 느꼈기 때문에 나는 이것을 끝냈습니다.

  List<Task> x = new List<Task>();
  foreach(var s in myCollectionOfObject)
  {
      // Note there is no await here. Just collection the Tasks
      x.Add(s.DoSomethingAsync());
  }
  await Task.WhenAll(x);

이렇게하면 작업이 차례로 실행되거나 WhenAll이 모든 작업을 한꺼번에 시작합니까?
Vinicius Gualberto

내가 알 수있는 한 "DoSomethingAsync ()"를 호출하면 모두 시작됩니다. 그러나 WhenAll이 호출 될 때까지 아무것도 차단하지 않습니다.
크리스 M.

첫 번째 "DoSomethingAsync ()"가 호출 될 때 의미합니까?
Vinicius Gualberto

1
@성유. DoSomethingAsync ()가 처음 대기 할 때까지 차단됩니다. 이것이 실행을 루프로 다시 전송하기 때문입니다. 이 동기이고 수익 태스크 경우, 모든 코드가 차례로 실행되고 WhenAll이 완료 모든 작업을 기다립니다
사이먼 Belanger 보낸

1

Parallel.ForEach가 부적절하게 사용되는 것을 보았 으며이 질문의 예가 ​​도움이 될 것이라고 생각했습니다.

콘솔 앱에서 아래 코드를 실행하면 Parallel.ForEach에서 실행 된 작업이 호출 스레드를 차단하지 않는 방법을 볼 수 있습니다. 결과 (긍정적 또는 부정적)에 신경 쓰지 않는다면 괜찮을 수 있지만 결과가 필요하다면 Task.WhenAll을 사용해야합니다.

using System;
using System.Linq;
using System.Threading.Tasks;

namespace ParrellelEachExample
{
    class Program
    {
        static void Main(string[] args)
        {
            var indexes = new int[] { 1, 2, 3 };

            RunExample((prefix) => Parallel.ForEach(indexes, (i) => DoSomethingAsync(i, prefix)),
                "Parallel.Foreach");

            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("*You'll notice the tasks haven't run yet, because the main thread was not blocked*");
            Console.WriteLine("Press any key to start the next example...");
            Console.ReadKey();

            RunExample((prefix) => Task.WhenAll(indexes.Select(i => DoSomethingAsync(i, prefix)).ToArray()).Wait(),
                "Task.WhenAll");
            Console.WriteLine("All tasks are done.  Press any key to close...");
            Console.ReadKey();
        }

        static void RunExample(Action<string> action, string prefix)
        {
            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine($"{Environment.NewLine}Starting '{prefix}'...");
            action(prefix);
            Console.WriteLine($"{Environment.NewLine}Finished '{prefix}'{Environment.NewLine}");
        }


        static async Task DoSomethingAsync(int i, string prefix)
        {
            await Task.Delay(i * 1000);
            Console.WriteLine($"Finished: {prefix}[{i}]");
        }
    }
}

결과는 다음과 같습니다.

여기에 이미지 설명 입력

결론:

Parallel.ForEach를 Task와 함께 사용하면 호출 스레드가 차단되지 않습니다. 결과에 관심이 있다면 작업을 기다려야합니다.

~ 건배

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.