[C#] Parallel.ForEach 대 Task.Run 및 Task.WhenAll

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



답변

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

그러나 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));


답변

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

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

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

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

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


답변

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

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


답변

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와 함께 사용하면 호출 스레드가 차단되지 않습니다. 결과에 관심이 있다면 작업을 기다려야합니다.

~ 건배


답변