c# .net parallel-processing async-await parallel.foreach

c# - ¿Está bien hacer algo asincrónico/esperar dentro de algún código.NET Parallel.ForEach()?



parallel-processing async-await (4)

Como señaló @Sriram Sakthivel, existen algunos problemas con el uso de Parallel.ForEach con lambdas asíncronas. ForEachASync Steven Toub puede hacer el equivalente. Él lo menciona aquí , pero aquí está el código:

public static class Extensions { public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body) { return Task.WhenAll( from partition in Partitioner.Create(source).GetPartitions(dop) select Task.Run(async delegate { using (partition) while (partition.MoveNext()) await body(partition.Current); })); } }

Utiliza la clase Partitioner para crear un particionador de equilibrio de carga ( doco ), y le permite especificar cuántos subprocesos desea ejecutar con el parámetro dop . para ver la diferencia entre él y Parallel.ForEach . Pruebe el siguiente código.

class Program { public static async Task GetStuffParallelForEach() { var data = Enumerable.Range(1, 10); Parallel.ForEach(data, async i => { await Task.Delay(1000 * i); Console.WriteLine(i); }); } public static async Task GetStuffForEachAsync() { var data = Enumerable.Range(1, 10); await data.ForEachAsync(5, async i => { await Task.Delay(1000 * i); Console.WriteLine(i); }); } static void Main(string[] args) { //GetStuffParallelForEach().Wait(); // Finished printed before work is complete GetStuffForEachAsync().Wait(); // Finished printed after all work is done Console.WriteLine("Finished"); Console.ReadLine(); }

si ejecuta GetStuffForEachAsync el programa espera a que finalice todo el trabajo. Si ejecuta GetStuffParallelForEach , la línea Finished se imprimirá antes de que finalice el trabajo.

Dado el siguiente código, ¿está bien hacer async/await dentro de un Parallel.ForEach ?

p.ej.

Parallel.ForEach(names, name => { // Do some stuff... var foo = await GetStuffFrom3rdPartyAsync(name); // Do some more stuff, with the foo. });

o hay algunos gotcha de los que tengo que ser consciente?

EDITAR: No tengo idea si esto compila, por cierto. Solo código Pseduo ... pensando en voz alta.


No, no tiene sentido combinar la async con Paralell.Foreach .

Considere el siguiente ejemplo:

private void DoSomething() { var names = Enumerable.Range(0,10).Select(x=> "Somename" + x); Parallel.ForEach(names, async(name) => { await Task.Delay(1000); Console.WriteLine("Name {0} completed",name); }); Console.WriteLine("Parallel ForEach completed"); }

¿Qué salida esperarás?

Name Somename3 completed Name Somename8 completed Name Somename4 completed ... Parallel ForEach completed

Eso no es lo que sucederá. Saldrá:

Parallel ForEach completed Name Somename3 completed Name Somename8 completed Name Somename4 completed ...

¿Por qué? Porque cuando ForEach hits primero await el método realmente regrese, Parallel.ForEach no sabe que es asincrónico y se ejecutó hasta su finalización. El código después de await ejecuta como continuación en otro hilo, no "hilo de procesamiento Paralell"

Stephen toub se dirigió a esto aquí


Por el nombre, GetStuffFrom3rdPartyAsync que GetStuffFrom3rdPartyAsync está GetStuffFrom3rdPartyAsync E / S. La clase Parallel es específicamente para código vinculado a CPU.

En el mundo asincrónico, puede iniciar varias tareas y luego (asincrónicamente) esperar a que se completen todas con Task.WhenAll . Como está comenzando con una secuencia, probablemente sea más fácil proyectar cada elemento a una operación asincrónica y luego esperar todas esas operaciones:

await Task.WhenAll(names.Select(async name => { // Do some stuff... var foo = await GetStuffFrom3rdPartyAsync(name); // Do some more stuff, with the foo. }));


Una alternativa cercana podría ser esta:

static void ForEach<T>(IEnumerable<T> data, Func<T, Task> func) { var tasks = data.Select(item => Task.Run(() => func(item))); Task.WaitAll(tasks.ToArray()); } // ... ForEach(names, name => GetStuffFrom3rdPartyAsync(name));

Idealmente, no deberías estar usando una llamada de bloqueo como Task.WaitAll , si puedes hacer que toda la cadena de métodos haga llamadas async , "completamente Task.WaitAll " en la pila de llamadas actual:

var tasks = data.Select(item => Task.Run(() => func(item))); await Task.WhenAll(tasks.ToArray());

Además, si no realiza ningún trabajo vinculado a CPU dentro de GetStuffFrom3rdPartyAsync , Task.Run puede ser redundante:

var tasks = data.Select(item => func(item));