Étant donné trois tâches - FeedCat()
, SellHouse()
et BuyCar()
, il y a deux cas intéressants: soit ils se terminent tous de manière synchrone (pour une raison quelconque, peut-être la mise en cache ou une erreur), soit ils ne le font pas.
Disons que nous avons, à partir de la question:
Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
// what here?
}
Maintenant, une approche simple serait:
Task.WhenAll(x, y, z);
mais ... ce n'est pas pratique pour traiter les résultats; nous voudrions généralement await
:
async Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
await Task.WhenAll(x, y, z);
// presumably we want to do something with the results...
return DoWhatever(x.Result, y.Result, z.Result);
}
mais cela fait beaucoup de surcharge et alloue divers tableaux (y compris le params Task[]
tableau) et listes (en interne). Cela fonctionne, mais ce n'est pas génial IMO. À bien des égards, il est plus simple d'utiliser une async
opération et await
chacune à son tour:
async Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
// do something with the results...
return DoWhatever(await x, await y, await z);
}
Contrairement à certains des commentaires ci-dessus, l'utilisation await
au lieu de neTask.WhenAll
fait aucune différence sur la façon dont les tâches s'exécutent (simultanément, séquentiellement, etc.). Au plus haut niveau, il Task.WhenAll
est antérieur au bon support du compilateur pour async
/ await
et était utile lorsque ces choses n'existaient pas . Il est également utile lorsque vous avez un tableau arbitraire de tâches, plutôt que 3 tâches discrètes.
Mais: nous avons toujours le problème que async
/ await
génère beaucoup de bruit de compilation pour la suite. S'il est probable que les tâches puissent effectivement se terminer de manière synchrone, nous pouvons optimiser cela en créant un chemin synchrone avec une solution de secours asynchrone:
Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
if(x.Status == TaskStatus.RanToCompletion &&
y.Status == TaskStatus.RanToCompletion &&
z.Status == TaskStatus.RanToCompletion)
return Task.FromResult(
DoWhatever(a.Result, b.Result, c.Result));
// we can safely access .Result, as they are known
// to be ran-to-completion
return Awaited(x, y, z);
}
async Task Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
return DoWhatever(await x, await y, await z);
}
Cette approche de "chemin de synchronisation avec repli asynchrone" est de plus en plus courante, en particulier dans le code haute performance où les exécutions synchrones sont relativement fréquentes. Notez que cela n'aidera pas du tout si l'achèvement est toujours véritablement asynchrone.
Autres choses qui s'appliquent ici:
avec C # récent, un modèle commun est que la async
méthode de secours est généralement implémentée en tant que fonction locale:
Task<string> DoTheThings() {
async Task<string> Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
return DoWhatever(await a, await b, await c);
}
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
if(x.Status == TaskStatus.RanToCompletion &&
y.Status == TaskStatus.RanToCompletion &&
z.Status == TaskStatus.RanToCompletion)
return Task.FromResult(
DoWhatever(a.Result, b.Result, c.Result));
// we can safely access .Result, as they are known
// to be ran-to-completion
return Awaited(x, y, z);
}
préfèrent ValueTask<T>
à Task<T>
s'il y a une bonne chance de choses jamais tout à fait synchrone avec beaucoup de différentes valeurs de retour:
ValueTask<string> DoTheThings() {
async ValueTask<string> Awaited(ValueTask<Cat> a, Task<House> b, Task<Tesla> c) {
return DoWhatever(await a, await b, await c);
}
ValueTask<Cat> x = FeedCat();
ValueTask<House> y = SellHouse();
ValueTask<Tesla> z = BuyCar();
if(x.IsCompletedSuccessfully &&
y.IsCompletedSuccessfully &&
z.IsCompletedSuccessfully)
return new ValueTask<string>(
DoWhatever(a.Result, b.Result, c.Result));
// we can safely access .Result, as they are known
// to be ran-to-completion
return Awaited(x, y, z);
}
si possible, préférer IsCompletedSuccessfully
à Status == TaskStatus.RanToCompletion
; cela existe maintenant dans .NET Core pour Task
, et partout pourValueTask<T>