Parallel.ForEach vs Task.Run dan Task.WhenAll


158

Apa perbedaan antara menggunakan Parallel.ForEach atau Task.Run () untuk memulai serangkaian tugas secara tidak sinkron?

Versi 1:

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

Versi 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
Saya pikir kode fragmen-2 akan hampir sama dengan yang 1 jika Anda digunakan Task.WaitAllsebagai pengganti Task.WhenAll.
avo

15
Harap perhatikan juga bahwa yang kedua akan melakukan DoSomething ("s3") tiga kali dan itu tidak akan menghasilkan hasil yang sama! stackoverflow.com/questions/4684320/…
Nullius


@Dan: perhatikan bahwa Versi 2 menggunakan async / menunggu, yang berarti ini adalah pertanyaan yang berbeda. Async / await diperkenalkan dengan VS 2012, 1,5 tahun setelah kemungkinan duplikat utas ditulis.
Petter T

Jawaban:


159

Dalam kasus ini, metode kedua akan secara sinkron menunggu tugas diselesaikan alih-alih diblokir.

Namun, ada kelemahan untuk digunakan Task.Rundalam loop-With Parallel.ForEach, ada Partitioneryang dibuat untuk menghindari membuat lebih banyak tugas daripada yang diperlukan. Task.Runakan selalu membuat satu tugas per item (karena Anda melakukan ini), tetapi Parallelkumpulan kelas berfungsi sehingga Anda membuat lebih sedikit tugas daripada total item pekerjaan. Ini dapat memberikan kinerja keseluruhan yang jauh lebih baik, terutama jika loop body memiliki sejumlah kecil pekerjaan per item.

Jika demikian, Anda dapat menggabungkan kedua opsi dengan menulis:

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

Perhatikan bahwa ini juga dapat ditulis dalam bentuk yang lebih pendek ini:

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

1
Jawaban yang bagus, saya ingin tahu apakah Anda bisa mengarahkan saya ke bahan bacaan yang bagus tentang topik ini?
Dimitar Dimitrov

@DimitarDimitrov Untuk barang-barang TPL umum, reedcopsey.com/series/parallelism-in-net4
Reed Copsey

1
Paralel saya. ForEach konstruk sedang crash aplikasi saya. Saya melakukan beberapa pemrosesan gambar berat di dalamnya. Namun, ketika saya menambahkan Task.Run (() => Parallel.ForEach (....)); Itu berhenti menabrak. Bisakah Anda jelaskan mengapa? Harap dicatat saya membatasi opsi paralel ke jumlah core pada sistem.
monkeyjumps

3
Bagaimana jika DoSomethingitu async void DoSomething?
Francesco Bonizzi

1
Bagaimana dengan async Task DoSomething?
Shawn Mclean

37

Versi pertama akan secara sinkron memblokir utas panggilan (dan menjalankan beberapa tugas di atasnya).
Jika ini adalah utas UI, ini akan membekukan UI.

Versi kedua akan menjalankan tugas secara tidak sinkron di kumpulan utas dan melepaskan utas panggilan sampai selesai.

Ada juga perbedaan dalam algoritma penjadwalan yang digunakan.

Perhatikan bahwa contoh kedua Anda dapat disingkat menjadi

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

2
bukankah seharusnya begitu await Task.WhenAll(strings.Select(async s => await Task.Run(() => DoSomething(s)));? Saya memiliki masalah ketika mengembalikan tugas (bukannya menunggu) terutama ketika pernyataan seperti usingterlibat untuk membuang objek.
Martín Coll

Panggilan Parallel.ForEach saya menyebabkan UI saya mogok saya menambahkan Task.Run (() => Parallel.ForEach (....)); untuk itu dan itu diselesaikan crash.
monkeyjumps

1

Saya akhirnya melakukan ini, karena rasanya lebih mudah dibaca:

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

Dengan cara ini Anda melakukan Tugas sedang dieksekusi satu demi satu atau WhenAll memulai semuanya sekaligus?
Vinicius Gualberto

Sejauh yang saya tahu, semuanya dimulai ketika saya memanggil "DoSomethingAsync ()". Namun, tidak ada yang menghalangi mereka, sampai WhenAll dipanggil.
Chris M.

Maksud Anda saat "DoSomethingAsync ()" pertama dipanggil?
Vinicius Gualberto

1
@ Chris. Ini akan diblokir sampai menunggu pertama DoSomethingAsync () karena inilah yang akan mentransfer eksekusi kembali ke loop Anda. Jika itu sinkron dan Anda mengembalikan Tugas, semua kode akan dijalankan satu demi satu dan WhenAll akan menunggu semua Tugas selesai
Simon Belanger

0

Saya telah melihat Paralel. ForEach digunakan secara tidak tepat, dan saya menemukan contoh dalam pertanyaan ini akan membantu.

Saat Anda menjalankan kode di bawah ini di aplikasi Konsol, Anda akan melihat bagaimana tugas dieksekusi dalam Paralel. FOREach tidak memblokir utas panggilan. Ini bisa baik-baik saja jika Anda tidak peduli dengan hasilnya (positif atau negatif) tetapi jika Anda memang membutuhkan hasilnya, Anda harus memastikan untuk menggunakan 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}]");
        }
    }
}

Inilah hasilnya:

masukkan deskripsi gambar di sini

Kesimpulan:

Menggunakan Paralel. FOREach dengan Tugas tidak akan memblokir utas panggilan. Jika Anda peduli dengan hasilnya, pastikan untuk menunggu tugas.

~ Ceria

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.