Necesito modificar un programa existente y contiene el siguiente código:
var inputs = events.Select(async ev => await ProcessEventAsync(ev))
.Select(t => t.Result)
.Where(i => i != null)
.ToList();
Pero esto me parece muy extraño, en primer lugar, el uso de async
y await
en la selección. De acuerdo con esta respuesta por Stephen Cleary que debe ser capaz de soltar esos.
Luego el segundo Select
que selecciona el resultado. ¿No significa esto que la tarea no es asíncrona en absoluto y se realiza de forma sincrónica (tanto esfuerzo para nada), o la tarea se realizará de forma asincrónica y cuando se realiza el resto de la consulta se ejecuta?
¿Debo escribir el código anterior como el siguiente según otra respuesta de Stephen Cleary :
var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)));
var inputs = tasks.Where(result => result != null).ToList();
y es completamente igual a esto?
var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
.Where(result => result != null).ToList();
Mientras estoy trabajando en este proyecto, me gustaría cambiar el primer ejemplo de código, pero no estoy demasiado interesado en cambiar (aparentemente trabajando) el código asíncrono. ¿Tal vez solo me estoy preocupando por nada y las 3 muestras de código hacen exactamente lo mismo?
ProcessEventsAsync tiene este aspecto:
async Task<InputResult> ProcessEventAsync(InputEvent ev) {...}
fuente
Task<InputResult>
conInputResult
ser una clase personalizada.Select
los resultados de las tareas anteriores a suWhere
.Result
propiedad de la tareaRespuestas:
La llamada a
Select
es válida. Estas dos líneas son esencialmente idénticas:(Hay una pequeña diferencia con respecto a cómo se generaría una excepción síncrona
ProcessEventAsync
, pero en el contexto de este código no importa en absoluto).Significa que la consulta está bloqueando. Por lo tanto, no es realmente asíncrono.
Desglosándolo:
primero comenzará una operación asincrónica para cada evento. Entonces esta línea:
esperará a que esas operaciones se completen una a la vez (primero espera la operación del primer evento, luego la siguiente, luego la siguiente, etc.).
Esta es la parte que no me importa, porque bloquea y también incluye cualquier excepción
AggregateException
.Sí, esos dos ejemplos son equivalentes. Ambos comienzan todas las operaciones asincrónicas (
events.Select(...)
), luego esperan asincrónicamente a que se completen todas las operaciones en cualquier orden (await Task.WhenAll(...)
), luego continúan con el resto del trabajo (Where...
).Ambos ejemplos son diferentes del código original. El código original está bloqueando y envolverá las excepciones
AggregateException
.fuente
AggregateException
, ¿obtendría múltiples excepciones separadas en el segundo código?Result
eso estaría envueltoAggregateException
.stuff.Select(x => x.Result);
conawait Task.WhenAll(stuff)
El código existente funciona, pero bloquea el hilo.
crea una nueva tarea para cada evento, pero
bloquea el hilo esperando que finalice cada nueva tarea.
Por otro lado, su código produce el mismo resultado pero se mantiene asíncrono.
Solo un comentario en tu primer código. Esta línea
producirá una sola tarea, por lo que la variable debe nombrarse en singular.
Finalmente su último código hace lo mismo pero es más sucinto
Para referencia: Task.Wait / Task.WhenAll
fuente
tasks
variable, tiene toda la razón. Elección horrible, ni siquiera son tareas, ya que se les espera de inmediato. Sin embargoCon los métodos actuales disponibles en Linq, se ve bastante feo:
Esperemos que las siguientes versiones de .NET presenten herramientas más elegantes para manejar colecciones de tareas y tareas de colecciones.
fuente
Usé este código:
Me gusta esto:
fuente
Func<TSource, Task<TResult>> method
contener loother params
mencionado en el segundo bit de código?Select()
, por lo que es un complemento elegante.Prefiero esto como un método de extensión:
Para que se pueda usar con el método de encadenamiento:
fuente
Wait
cuando en realidad no está esperando. Está creando una tarea que se completa cuando se completan todas las tareas. LlámaloWhenAll
, como elTask
método que emula. Tampoco tiene sentido que el método seaasync
. Simplemente llameWhenAll
y termine con eso.WhenAll
devuelve una lista evaluada (no se evalúa perezosamente), se puede hacer un argumento para usar elTask<T[]>
tipo de devolución para significar eso. Cuando se espera, esto aún podrá usar Linq, pero también comunica que no es perezoso.