Tengo el siguiente código:

List<Task<bool>> tasks = tasksQuery.ToList();
while (tasks.Any())
{
    Task<bool> completedTask = await Task.WhenAny(tasks);
    if (await completedTask)
        return true;

    tasks.Remove(completedTask);
}

Lanza tareas en paralelo. Cuando la primera tarea completada devuelve verdadero, el método devuelve verdadero.

Mis preguntas son:

  1. ¿Qué sucede con todas las tareas restantes que se han iniciado y probablemente todavía se están ejecutando en segundo plano?
  2. ¿Es este el enfoque correcto para ejecutar código que es asíncrono, paralelo y debería regresar después de que ocurra la primera condición, o es mejor lanzarlos uno por uno y esperar singularmente?
40
Aleksander Bethke 2 ene. 2017 a las 14:57
5
Recomiendo usar un token de cancelación común. Una vez finalizada la primera tarea, debe enviar la cancelación a todas las demás tareas.
 – 
Leonid Malyshev
2 ene. 2017 a las 15:00
6
Esta pregunta es un poco amplia. "¿Lo que sucede?" bueno, siguen ejecutando hasta que terminan ... "¿Es este el enfoque correcto?" ¿Cómo deberíamos saberlo? No sabemos qué están haciendo esas tareas. Si los lanza uno por uno y espera a que cada uno verifique, ya no es paralelo o asincrónico y la pregunta se contradice ... es posible que desee pasar un token de cancelación si le preocupa que las tomas restantes estén desperdiciando recursos o causando otro daño ...
 – 
René Vogt
2 ene. 2017 a las 15:02
3
¿Crees que Task.WhenAll sería una buena opción para lo que quieres?
 – 
heltonbiker
2 ene. 2017 a las 15:05
La idea era esperar a cada uno de forma asincrónica pero no paralela. El enfoque de token parece el mejor. Gracias.
 – 
Aleksander Bethke
2 ene. 2017 a las 16:04
2
Bueno, una cosa es segura, cuando los crea todos por adelantado y los ejecuta en paralelo, entonces ciertamente no es "crear 'lo suficiente' para no desperdiciar recursos". En este esquema, el código es realmente libre de desperdiciar TODOS los trabajos / recursos que pueda. En realidad, eso sigue siendo válido incluso si implementa el token de cancelación; con eso, el código seguirá siendo libre para usar todos los trabajos / recursos que pueda, solo para cancelar / liberar / deshacer / liberar / etc.cuando se complete el primero. Esa es casi una definición de compensación entre recursos y velocidad: desperdiciar trabajos para obtener el resultado un poco más rápido.
 – 
quetzalcoatl
2 ene. 2017 a las 16:43

1 respuesta

La mejor respuesta

Por cierto, solo estoy leyendo Concurrencia en C # CookBook. , de Stephen Cleary, y supongo que puedo consultar algunas partes del libro aquí.

De Receta 2.5 - Discusión , tenemos

Cuando se complete la primera tarea, considere si desea cancelar las tareas restantes. Si las otras tareas no se cancelan pero tampoco se esperan nunca, se abandonan. Las tareas abandonadas se ejecutarán hasta su finalización y sus resultados se ignorarán . También se ignorará cualquier excepción de esas tareas abandonadas.

Otro antipatrón para Task.WhenAny es manejar tareas a medida que se completan. Al principio, parece un enfoque razonable mantener una lista de tareas y eliminar cada tarea de la lista a medida que se completa . El problema con este enfoque es que se ejecuta en el tiempo O (N ^ 2), cuando existe un algoritmo O (N).

Además de eso, creo que WhenAny es sin duda el enfoque correcto. Solo considere el siguiente enfoque de Leonid de pasar el mismo CancellationToken para todas las tareas y cancelarlas después de la primera uno vuelve. E incluso eso, solo si el costo de estas operaciones realmente está gravando al sistema.

27
MarredCheese 20 sep. 2021 a las 05:01
1
Muchas gracias. Es una pena que me haya perdido la receta del libro de Stephen. En mi caso, los dejaré fuera porque esas tareas no realizan ningún trabajo pesado (1 llamada HTTP y algunos trabajos de deserialización cada una). En caso de que Tasks esté haciendo un trabajo de impuestos sobre los recursos, usaré tokens de cancelación y algunos usuarios lo sugieren en este hilo. Gracias de nuevo.
 – 
Aleksander Bethke
2 ene. 2017 a las 16:36
3
Bonito. Tenga en cuenta que lo más probable es que utilice un único CancellationToken para todas las tareas.
 – 
heltonbiker
2 ene. 2017 a las 16:56
9
¿Qué es el enfoque O (N)?
 – 
nawfal
29 may. 2018 a las 10:11
1
Fui a GitHub para leer el código de la clase Task y no veo una razón por la que se convierte en O (N ^ 2) solo porque usas el método WhenAny
 – 
Rafa
17 dic. 2019 a las 15:50
1
Aquí hay más información sobre el tema O (n ^ 2): devblogs. microsoft.com/pfxteam/…
 – 
Will Dean
6 abr. 2020 a las 01:42