Task<T> est utilisé dans le cas où une tâche retourne un résultat. Il y a plusieurs façons de récupérer ce dernier mais toutes ne se valent pas…
Liens rapides vers la série complète
Task, qui es-tu ? partie 1 – Task la fuyante…
Task, qui es-tu ? partie 2 – Construction
Task, qui es-tu ? partie 3 – AsyncState
Task, qui es-tu ? partie 4 – CreationOptions
Task, qui es-tu ? partie 5 – Delegate et Promise Tasks
Task, qui es-tu ? partie 6 – Unicité de l’ID de tâche
Task, qui es-tu ? partie 7 – Attendre des Tasks
Task, qui es-tu ? partie 8 – Task générique et Result
Task, qui es-tu ? partie 9 – Les continuations
Task, qui es-tu ? partie 10 – TaskFactory, Annulation de tâche…
Task, qui es-tu ? partie 11 – Delay, Yield, FromResult…
Task, qui es-tu ? partie 12 – Les patterns de l’Asynchrone
Task et Task<T>
Récupérer les informations retournée par une Task<T> est un passage obligé, soit parce que cela coule de source (demander un résultat et ne pas en ternir compte c’est écrire du code qui peut être mis en commentaire !), soit parce qu’il faut au minimum s’assurer que tout s’est bien passé ou traiter les éventuelles erreurs. Et dans ce cas on entend ici par “résultat” tous ces différents types de résultat (une exception lue via la propriété Exception est un résultat comme un autre).
Result
Cette propriété est du type passé à l’appel de la tâche et n’existe bien entendu que dans le cas de Task<T>.
De la même façon que Wait, lire Result va bloquer de façon synchrone le thread appelant jusqu’à temps que la tâche soit complétée. Ce n’est donc pas une bonne idée que de procéder de la sorte car comme pour Wait les deadlocks ne sont pas loin !
De plus si des erreurs se produisent pendant l’accès à Result elles seront agrégées dans une AggregateException ce qui en compliquera le traitement.
Exception
Comme son nom le laisse supposer cette propriété retourne les exceptions de la tâche sous une forme agrégée. A la différence de Result et Wait la lecture de cette propriété n’est pas bloquante. Elle retourne null en permanence sauf si la tâche est terminée et qu’elle a généré des erreurs. Cette propriété se retrouve aussi bien dans Task que Task<T> bien entendu.
GetAwaiter().GetResult()
Le membre GetAwaiter a été ajouté dans Task dans .NET 4.5 uniquement pour les besoins de await mais en théorie rien n’interdit de s’en servir directement. Il n’y a que peu de différence avec Result mais elle peut avoir son importance : les exceptions ne seront pas agrégées ce qui peut être plus pratique à traiter.
await
Ce n’est bien entendu pas un membre de Task mais comme il est question des résultats retournés par une tâches il semble essentiel de rappeler que await attendra de façon asynchrone un résultat en provenance de Task (ou Task<T>) et qu’il est donc non bloquant. Dans tous les cas sauf contrainte spécifique qui resterait à définir et justifier un résultat de Task s’obtient par await. C’est de loin la méthode la plus efficace. Elle est préférable à toutes les autres, que cela soit Wait, Result, Exception or GetAwaiter.
Conclusion
Finalement l’utilisation de Task c’est facile, il ne faut pratiquement rien utiliser sauf un await sur la tâche… On ne créée pas d’instances, on await juste les données.
Il est clair qu’on est parti de loin et il y a encore pas longtemps il fallait faire de drôles de détours réservés à des spécialistes pour un code difficile à lire et à maintenir. Avec les dernières versions du Framework, Parallel, la TPL, async/await et Task, tirer partie des multicoeurs est devenu un jeu d’enfant…
Mais ce n’est pas fini, puisque dans la prochaine partie nous parlerons justement des continuations !
Stay Tuned !