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