web-dev-qa-db-fra.com

Parallel.ForEach vs Task.Factory.StartNew

Quelle est la différence entre les extraits de code ci-dessous? Les deux n'utiliseront-ils pas de thread threadpool?

Par exemple, si je veux appeler une fonction pour chaque élément d'une collection,

Parallel.ForEach<Item>(items, item => DoSomething(item));

vs

foreach(var item in items)
{
  Task.Factory.StartNew(() => DoSomething(item));
}
256
stackoverflowuser

Le premier est une bien meilleure option.

Parallel.ForEach, en interne, utilise un Partitioner<T> pour distribuer votre collection en éléments de travail. Il ne fera pas une tâche par article, mais plutôt en lots pour réduire les frais généraux impliqués.

La deuxième option planifiera un seul Task par article de votre collection. Bien que les résultats soient (presque) identiques, cela entraînera beaucoup plus de frais généraux que nécessaire, en particulier pour les grandes collections, et ralentira la durée d'exécution globale.

FYI - Le partitionneur utilisé peut être contrôlé en utilisant le paramètre approprié surcharge sur Parallel.ForEach , si désiré. Pour plus de détails, voir Partitioners personnalisés sur MSDN.

La principale différence, au moment de l'exécution, est que le second agira de manière asynchrone. Cela peut être dupliqué en utilisant Parallel.ForEach en faisant:

_Task.Factory.StartNew( () => Parallel.ForEach<Item>(items, item => DoSomething(item)));
_

En faisant cela, vous tirez toujours parti des partitionneurs, mais ne bloquez pas avant la fin de l'opération.

288
Reed Copsey

J'ai fait une petite expérience d'exécution d'une méthode "1000000000" fois avec des objets "Parallel.For" et une avec des objets "Task".

J'ai mesuré le temps processeur et trouvé Parallel plus efficace. Parallel.For divise votre tâche en petits éléments de travail et les exécute de manière optimale sur tous les cœurs. Lors de la création de nombreux objets de tâche (FYI TPL utilisera le regroupement de threads en interne) déplacera chaque exécution sur chaque tâche, ce qui créera plus de stress dans la zone, comme le montre l'expérience ci-dessous.

J'ai également créé une petite vidéo qui explique la TPL de base et montre également comment Parallel.For utilise votre cœur plus efficacement http://www.youtube.com/watch?v=No7QqSc5cl8 par rapport aux tâches normales et des fils.

Expérience 1

Parallel.For(0, 1000000000, x => Method1());

Expérience 2

for (int i = 0; i < 1000000000; i++)
{
    Task o = new Task(Method1);
    o.Start();
}

Processor time comparison

83
Shivprasad Koirala

Parallel.ForEach optimisera (ne pourra même pas démarrer de nouveaux threads) et bloquera jusqu'à ce que la boucle soit terminée, et Task.Factory créera explicitement une nouvelle instance de tâche pour chaque élément, puis retournera avant la fin (tâches asynchrones). Parallel.Foreach est beaucoup plus efficace.

17
Sogger

À mon avis, le scénario le plus réaliste est celui où les tâches doivent être exécutées avec une lourde tâche. L'approche de Shivprasad est davantage axée sur la création d'objets/l'allocation de mémoire que sur l'informatique elle-même. J'ai fait une recherche en appelant la méthode suivante:

public static double SumRootN(int root)
{
    double result = 0;
    for (int i = 1; i < 10000000; i++)
        {
            result += Math.Exp(Math.Log(i) / root);
        }
        return result; 
}

L'exécution de cette méthode prend environ 0.5sec.

Je l'ai appelé 200 fois en utilisant Parallel:

Parallel.For(0, 200, (int i) =>
{
    SumRootN(10);
});

Ensuite, je l’ai appelée 200 fois en utilisant l’ancienne méthode:

List<Task> tasks = new List<Task>() ;
for (int i = 0; i < loopCounter; i++)
{
    Task t = new Task(() => SumRootN(10));
    t.Start();
    tasks.Add(t);
}

Task.WaitAll(tasks.ToArray()); 

Le premier cas s'est achevé en 26656ms, le second en 24478ms. Je l'ai répété plusieurs fois. À chaque fois, la deuxième approche est marginalement plus rapide.

9
user1089583