Parallel.ForEach y async-await [duplicado]
Tenía ese método:
public async Task<MyResult> GetResult()
{
MyResult result = new MyResult();
foreach(var method in Methods)
{
string json = await Process(method);
result.Prop1 = PopulateProp1(json);
result.Prop2 = PopulateProp2(json);
}
return result;
}
Entonces decidí usar Parallel.ForEach
:
public async Task<MyResult> GetResult()
{
MyResult result = new MyResult();
Parallel.ForEach(Methods, async method =>
{
string json = await Process(method);
result.Prop1 = PopulateProp1(json);
result.Prop2 = PopulateProp2(json);
});
return result;
}
Pero ahora tengo un error:
Un módulo o controlador asincrónico se completó mientras aún estaba pendiente una operación asincrónica.
async
no funciona bien con ForEach
. En particular, su async
lambda se está convirtiendo en un async void
método. Hay varias razones para evitarloasync void
(como lo describo en un artículo de MSDN); uno de ellos es que no se puede detectar fácilmente cuándo se async
ha completado la lambda. ASP.NET verá que su código regresa sin completar el async void
método y (apropiadamente) generará una excepción.
Lo que probablemente desee hacer es procesar los datos simultáneamente , pero no en paralelo . El código paralelo casi nunca debería usarse en ASP.NET. Así es como se vería el código con el procesamiento concurrente asíncrono:
public async Task<MyResult> GetResult()
{
MyResult result = new MyResult();
var tasks = Methods.Select(method => ProcessAsync(method)).ToArray();
string[] json = await Task.WhenAll(tasks);
result.Prop1 = PopulateProp1(json[0]);
...
return result;
}
.NET 6 finalmente agregó Parallel.ForEachAsync , una forma de programar trabajo asincrónico que le permite controlar el grado de paralelismo:
var urlsToDownload = new []
{
"https://dotnet.microsoft.com",
"https://www.microsoft.com",
"https://twitter.com/shahabfar"
};
var client = new HttpClient();
var options = new ParallelOptions { MaxDegreeOfParallelism = 2 };
await Parallel.ForEachAsync(urlsToDownload, options, async (url, token) =>
{
var targetPath = Path.Combine(Path.GetTempPath(), "http_cache", url);
var response = await client.GetAsync(url, token);
// The request will be canceled in case of an error in another URL.
if (response.IsSuccessStatusCode)
{
using var target = File.OpenWrite(targetPath);
await response.Content.CopyToAsync(target);
}
});