J'ai besoin de modifier un programme existant et il contient le code suivant:
var inputs = events.Select(async ev => await ProcessEventAsync(ev))
.Select(t => t.Result)
.Where(i => i != null)
.ToList();
Mais cela me semble très étrange, tout d'abord l'utilisation de async
et await
dans le select. D'après cette réponse de Stephen Cleary, je devrais pouvoir les supprimer.
Puis le second Select
qui sélectionne le résultat. Cela ne signifie-t-il pas que la tâche n'est pas du tout asynchrone et est exécutée de manière synchrone (tant d'efforts pour rien), ou la tâche sera-t-elle exécutée de manière asynchrone et lorsqu'elle est terminée, le reste de la requête est exécuté?
Dois-je écrire le code ci-dessus comme suit selon une autre réponse de Stephen Cleary :
var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)));
var inputs = tasks.Where(result => result != null).ToList();
et est-ce complètement pareil?
var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
.Where(result => result != null).ToList();
Pendant que je travaille sur ce projet, j'aimerais changer le premier exemple de code, mais je ne suis pas trop désireux de changer (apparemment en train de travailler) le code async. Peut-être que je ne m'inquiète pour rien et que les 3 exemples de code font exactement la même chose?
ProcessEventsAsync ressemble à ceci:
async Task<InputResult> ProcessEventAsync(InputEvent ev) {...}
la source
Task<InputResult>
avecInputResult
être une classe personnalisée.Select
les résultats des tâches avant votreWhere
.Result
propriété de la tâcheRéponses:
L'appel à
Select
est valide. Ces deux lignes sont essentiellement identiques:(Il y a une différence mineure concernant la façon dont une exception synchrone serait lancée
ProcessEventAsync
, mais dans le contexte de ce code, cela n'a pas d'importance.)Cela signifie que la requête est bloquante. Ce n'est donc pas vraiment asynchrone.
Décomposer:
démarrera d'abord une opération asynchrone pour chaque événement. Puis cette ligne:
attendra que ces opérations se terminent une à la fois (il attend d'abord l'opération du premier événement, puis la suivante, puis la suivante, etc.).
C'est la partie qui ne m'intéresse pas, car elle bloque et englobe également toutes les exceptions
AggregateException
.Oui, ces deux exemples sont équivalents. Ils démarrent tous les deux toutes les opérations asynchrones (
events.Select(...)
), puis attendent de manière asynchrone que toutes les opérations se terminent dans n'importe quel ordre (await Task.WhenAll(...)
), puis poursuivent le reste du travail (Where...
).Ces deux exemples sont différents du code d'origine. Le code d'origine est bloquant et encapsulera les exceptions
AggregateException
.la source
AggregateException
, j'obtiendrais plusieurs exceptions distinctes dans le deuxième code?Result
ça serait enveloppéAggregateException
.stuff.Select(x => x.Result);
parawait Task.WhenAll(stuff)
Le code existant fonctionne, mais bloque le thread.
crée une nouvelle tâche pour chaque événement, mais
bloque le thread qui attend la fin de chaque nouvelle tâche.
En revanche, votre code produit le même résultat mais reste asynchrone.
Juste un commentaire sur votre premier code. Cette ligne
produira une seule tâche donc la variable doit être nommée au singulier.
Enfin votre dernier code fait la même chose mais est plus succinct
Pour référence: Task.Wait / Task.WhenAll
la source
tasks
variable, vous avez tout à fait raison. Choix horrible, ce ne sont même pas des tâches car elles sont attendues tout de suite. Je vais laisser la question telleAvec les méthodes actuelles disponibles dans Linq, cela semble assez moche:
Espérons que les versions suivantes de .NET proposeront des outils plus élégants pour gérer des collections de tâches et des tâches de collections.
la source
J'ai utilisé ce code:
comme ça:
la source
Select()
, il en va de même pour un élégant drop-in.async
etawait
à l'intérieur du premier lambda est redondant. La méthode SelectAsync peut simplement être écrite comme suit:return await Task.WhenAll(source.Select(method));
Je préfère cela comme méthode d'extension:
Pour qu'il soit utilisable avec le chaînage de méthodes:
la source
Wait
lorsqu'elle n'est pas en attente. Il s'agit de créer une tâche qui est terminée lorsque toutes les tâches sont terminées. Appelez-leWhenAll
, comme laTask
méthode qu'il émule. Il est également inutile que la méthode soitasync
. Il suffit d'appelerWhenAll
et d'en finir.WhenAll
renvoie une liste évaluée (elle n'est pas évaluée paresseusement), un argument peut être fait pour utiliser leTask<T[]>
type de retour pour signifier cela. Lorsqu'il est attendu, cela pourra toujours utiliser Linq, mais communique également qu'il n'est pas paresseux.