Parallel.ForEach กับ Task.Run และ Task.WhenAll


158

อะไรคือความแตกต่างระหว่างการใช้ 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);

3
ผมคิดว่าส่วนรหัสที่ 2 จะได้รับเกือบเท่ากับ 1 หนึ่งถ้าคุณใช้แทนTask.WaitAll Task.WhenAll
avo

15
โปรดทราบว่ารายการที่สองจะแสดง DoSomething ("s3") สามครั้งและจะไม่ให้ผลลัพธ์เดียวกัน! stackoverflow.com/questions/4684320/…
Nullius

1
สำเนาซ้ำที่เป็นไปได้ของParallel.ForEach vs Task.Factory.StartNew
Mohammad

@Dan: โปรดทราบว่าเวอร์ชัน 2 ใช้ async / await ซึ่งหมายความว่าเป็นคำถามที่แตกต่าง Async / await ได้รับการแนะนำให้รู้จักกับ VS 2012 1.5 ปีหลังจากมีการเขียนเธรดที่ซ้ำกันที่เป็นไปได้
Petter T

คำตอบ:


159

ในกรณีนี้วิธีที่สองจะรอแบบอะซิงโครนัสเพื่อให้งานเสร็จสมบูรณ์แทนที่จะปิดกั้น

อย่างไรก็ตามมีข้อเสียที่จะใช้Task.Runในวง - ด้วย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
My Parallel.ForEach build ของฉันขัดข้องแอปพลิเคชันของฉัน ฉันกำลังประมวลผลภาพจำนวนมากอยู่ข้างใน อย่างไรก็ตามเมื่อฉันเพิ่ม Task.Run (() => Parallel.ForEach (.... )); มันหยุดทำงานล้มเหลว คุณอธิบายได้ไหม โปรดทราบฉัน จำกัด ตัวเลือกแบบขนานกับจำนวนของแกนในระบบ
monkeyjumps

3
เกิดอะไรขึ้นถ้าDoSomethingเป็นasync void DoSomething?
Francesco Bonizzi

1
เกี่ยวกับasync Task DoSomethingอะไร
Shawn Mclean

37

รุ่นแรกจะบล็อกเธรดการโทรพร้อมกัน (และเรียกใช้งานบางอย่างกับมัน)
หากเป็นเธรด 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 (.... )); ไปที่มันและมันได้รับการแก้ไข crashing
monkeyjumps

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

วิธีที่คุณกำลังทำภารกิจนี้กำลังถูกดำเนินการทีละรายการหรือเมื่อมีการเริ่มต้นทั้งหมดในครั้งเดียว
Vinicius Gualberto

เท่าที่ฉันสามารถบอกได้พวกเขาทั้งหมดเริ่มต้นเมื่อฉันเรียกว่า "DoSomethingAsync ()" อย่างไรก็ตามไม่มีสิ่งใดขวางกั้นพวกเขาจนกว่าจะมีการเรียก WhenAll
Chris M.

คุณหมายถึงเมื่อเรียกว่า "DoSomethingAsync ()" ตัวแรก?
Vinicius Gualberto

1
@ChrisM มันจะถูกปิดกั้นจนกว่าการรอคอยครั้งแรกของ DoSomethingAsync () เนื่องจากนี่คือสิ่งที่จะถ่ายโอนการดำเนินการกลับไปที่ลูปของคุณ ถ้ามันเป็นแบบซิงโครนัสและส่งคืนภารกิจของคุณรหัสทั้งหมดจะถูกเรียกใช้ทีละรายการและ WhenAll จะรอให้ภารกิจทั้งหมดเสร็จสมบูรณ์
Simon Belanger

0

ฉันเคยเห็น 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.