El método original es el ciclo foreach ineficiente que espera cada trabajo (que es una llamada de red vinculada a E/S):
foreach (var job in Jobs) { try { await DoJobAsync(job); //Pass job to external vendor API job.Succeeded = true; } catch (Exception) { //do nothing, let loop continue } } Ahora, para mejorar el rendimiento, quiero usar Task.WhenAll para procesar todos los trabajos sin bloqueos.
Sin embargo, debemos asegurarnos de que cada objeto de job solo tenga la propiedad Succeeded establecida en true si la tarea DoJobAsync no arroja una excepción.
Si hacemos esto:
await Task.WhenAll(Jobs.Select(j => { var task = DoJobAsync(j); j.Succeeded = true; return task; })); Tengo entendido que si alguna tarea de trabajo termina arrojando una excepción, esa propiedad aún se cambiará a true porque cada tarea individual no se espera a medida que se crea, lo que hace que el flujo de código pase directamente.
Sé que puedo capturar la Task devuelta por Task.WhenAll para tener acceso a una lista de todas las excepciones lanzadas, pero no puedo encontrar una manera de usarlas para rastrear el job que lanzó la excepción.
¿Cómo soluciono este problema?
Devuelve el resultado verdadero/falso del método DoJobAsync().
De esta manera puede devolver el resultado directamente.
await Task.WhenAll(Jobs.Select(j => { return DoJobAsync(j); }));Podría usar un delegado asíncrono como selector del operador Select :
await Task.WhenAll(Jobs.Select(async job => { await DoJobAsync(job); job.Succeeded = true; })); De esta forma, cada trabajo se proyectará en una Task que no será la DoJobAsync(job) original, sino una tarea contenedora que encapsula la lógica para actualizar la propiedad Succeeded . Esta propiedad se actualizará inmediatamente después de que la DoJobAsync(job) se complete correctamente.
Es posible que varios objetos Job tengan su propiedad Succeeded actualizada en paralelo. Depende de si un SynchronizationContext está instalado en el subproceso actual.