Paralel. ForHer vs Görev.Run ve Görev.Ne zaman


158

Bir dizi görevi eşzamansız olarak başlatmak için Parallel.ForEach veya Task.Run () kullanma arasındaki farklar nelerdir?

Versiyon 1:

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

Versiyon 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
Eğer Task.WaitAllyerine kullanırsanız 2. kod parçası neredeyse 1'e eşit olacağını düşünüyorum Task.WhenAll.
avo

15
Lütfen ikincisinin DoSomething ("s3") 'ü üç kez gerçekleştireceğini ve aynı sonucu vermeyeceğini unutmayın! stackoverflow.com/questions/4684320/…
Nullius


@ Dan: Sürüm 2'nin async / await kullandığını unutmayın, bu farklı bir soru anlamına gelir. Async / await, olası yinelenen iş parçacığının yazılmasından 1.5 yıl sonra VS 2012 ile tanıtıldı.
Petter T

Yanıtlar:


159

Bu durumda, ikinci yöntem eşzamansız olarak görevlerin engelleme yerine tamamlanmasını bekler.

Ancak, Task.Runbir döngüde kullanmak için bir dezavantaj vardır- İle Parallel.ForEach, Partitionergereğinden fazla görev yapmaktan kaçınmak için yaratılan bir vardır. Task.Runher öğe için her zaman tek bir görev yapar (bunu yaptığınızdan beri), ancak Parallelsınıf toplu işleri çalışır, böylece toplam iş öğelerinden daha az görev oluşturursunuz. Bu, özellikle ilmek gövdesi parça başına az miktarda iş varsa, daha iyi bir genel performans sağlayabilir.

Bu durumda, her iki seçeneği de yazarak birleştirebilirsiniz:

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

Bunun daha kısa biçimde de yazılabileceğini unutmayın:

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

1
Harika cevap, beni bu konuda iyi bir okuma materyaline yönlendirip yönlendiremeyeceğinizi merak ediyordum.
Dimitar Dimitrov

@DimitarDimitrov Genel TPL maddeleri için reedcopsey.com/series/parallelism-in-net4
Reed Copsey

1
Parallel.ForEach yapım uygulamam çöküyordu. İçinde ağır bir görüntü işleme gerçekleştiriyordum. Ancak, Task.Run (() => Parallel.ForEach (....)) eklediğimde; Çökmeyi durdurdu. Nedenini açıklayabilir misin? Paralel seçenekleri sistemdeki çekirdek sayısı ile sınırladığımı lütfen unutmayın.
monkeyjumps

3
Ya DoSomethingolduğunu async void DoSomething?
Francesco Bonizzi

1
Ne olmuş async Task DoSomething?
Shawn Mclean

37

İlk sürüm, çağrı dizisini eşzamanlı olarak engeller (ve bazı görevler üzerinde çalışır).
Bu bir kullanıcı arayüzü iş parçacığıysa, kullanıcı arayüzünü dondurur.

İkinci sürüm, görevleri iş parçacığı havuzunda eşzamansız olarak çalıştırır ve tamamlanana kadar çağıran iş parçacığını serbest bırakır.

Kullanılan programlama algoritmalarında da farklılıklar vardır.

İkinci örneğinizin kısaltılabileceğini unutmayın

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

2
öyle değil mi await Task.WhenAll(strings.Select(async s => await Task.Run(() => DoSomething(s)));? Görevleri döndürürken (beklemek yerine), özellikle usingnesnelerin imha edilmesi gibi ifadeler söz konusu olduğunda sorunlar yaşadım .
Martín Coll

Parallel.ForEach çağrı UI benim çökmesine neden oldu Task.Run (() => Parallel.ForEach (....)) ekledi; ve çökmesini çözdü.
monkeyjumps

1

Okumayı daha kolay hissettiği için bunu yaptım:

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

Bu şekilde yaptığınız Görevler birbiri ardına veya WhenAll hepsini bir kerede başlatıyor mu?
Vinicius Gualberto

Anlayabildiğim kadarıyla, hepsi "DoSomethingAsync ()" dediğimde başlatılır. Ancak WhenAll çağrılıncaya kadar hiçbir şey onları engellemez.
Chris M.

İlk "DoSomethingAsync ()" ne zaman çağrılır?
Vinicius Gualberto

1
@ChrisM. DoSomethingAsync () 'in ilk beklemesine kadar engellenecektir, çünkü yürütme döngünüze geri aktarılacaktır. Eşzamanlıysa ve bir Görevi iade ederseniz, tüm kodlar birbiri ardına çalıştırılır ve WhenAll tüm Görevlerin tamamlanmasını bekler
Simon Belanger

0

Parallel.ForEach'ın uygunsuz bir şekilde kullanıldığını gördüm ve bu sorunun bir örneğinin yardımcı olacağını düşündüm.

Bir Konsol uygulamasında aşağıdaki kodu çalıştırdığınızda, Parallel.ForEach uygulamasında yürütülen görevlerin çağrı dizisini nasıl engellemediğini göreceksiniz. Sonucu umursamıyorsanız (pozitif veya negatif) bu iyi olabilir, ancak sonuca ihtiyacınız varsa, Görev'i kullandığınızdan emin olmalısınız.

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}]");
        }
    }
}

İşte sonuç:

resim açıklamasını buraya girin

Sonuç:

Parallel.ForEach öğesini bir Görev ile kullanmak çağıran evreyi engellemez. Sonucu önemsiyorsanız görevleri beklediğinizden emin olun.

~ Alkış

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.