133 votes

Quelle est la différence entre renvoyer void et renvoyer une tâche ?

En examinant divers exemples de CTP Async C#, je vois des fonctions asynchrones qui renvoient void et d'autres qui renvoient l'élément non générique Task . Je comprends pourquoi le renvoi d'un Task<MyType> est utile pour renvoyer des données à l'appelant lorsque l'opération asynchrone est terminée, mais les fonctions que j'ai vues qui ont un type de retour de Task ne renvoient jamais de données. Pourquoi ne pas renvoyer void ?

217voto

Eric Lippert Points 300275

Les réponses de SLaks et de Killercam sont bonnes ; j'ai pensé ajouter un peu plus de contexte.

Votre première question porte essentiellement sur les méthodes qui peuvent être marquées. async .

Une méthode marquée comme async peut renvoyer void , Task o Task<T> . Quelles sont les différences entre eux ?

A Task<T> Une méthode asynchrone qui revient peut être attendue, et lorsque la tâche est terminée, elle propose un T.

A Task Les méthodes asynchrones qui reviennent peuvent être attendues, et lorsque la tâche se termine, la suite de la tâche est programmée pour être exécutée.

A void Une méthode asynchrone qui revient ne peut pas être attendue ; il s'agit d'une méthode "à utiliser et à oublier". Elle fonctionne de manière asynchrone et vous n'avez aucun moyen de savoir quand elle a terminé. C'est plus qu'un peu bizarre ; comme le dit SLaks, normalement vous ne feriez cela que lors de la création d'un gestionnaire d'événement asynchrone. L'événement se déclenche, le gestionnaire s'exécute ; personne ne va "attendre" la tâche renvoyée par le gestionnaire d'événement parce que les gestionnaires d'événements ne renvoient pas de tâches, et même s'ils le faisaient, quel code utiliserait la tâche pour quelque chose ? Ce n'est généralement pas le code utilisateur qui transfère le contrôle au gestionnaire en premier lieu.

Votre deuxième question, dans un commentaire, porte essentiellement sur ce qui peut être fait. await éd :

Quels types de méthodes peuvent être await ed ? Une méthode à retour nul peut-elle être await ed ?

Non, une méthode à retour nul ne peut pas être attendue. Le compilateur traduit await M() en un appel à M().GetAwaiter() donde GetAwaiter peut être une méthode d'instance ou une méthode d'extension. La valeur attendue doit être une valeur pour laquelle vous pouvez obtenir un attendeur ; il est clair qu'une méthode à retour nul ne produit pas une valeur à partir de laquelle vous pouvez obtenir un attendeur.

Task -Les méthodes à retour d'information peuvent produire des valeurs attendues. Nous prévoyons que des tiers voudront créer leurs propres implémentations de Task -comme des objets que l'on peut attendre, et vous pourrez les attendre. Cependant, vous ne serez pas autorisé à déclarer des objets async les méthodes qui renvoient autre chose que void , Task o Task<T> .

(MISE À JOUR : ma dernière phrase pourrait être falsifiée par une future version de C# ; il existe une proposition visant à autoriser des types de retour autres que les types de tâches pour les méthodes asynchrones).

(MISE À JOUR : la fonction mentionnée ci-dessus a été intégrée à C# 7).

7 votes

+1 Je pense que la seule chose qui manque est la différence de traitement des exceptions dans les méthodes asynchrones à retour nul.

0 votes

Excellente explication, merci Eric ! Je suis curieux de savoir à quoi João fait référence à propos des différences dans les exceptions.

10 votes

@JamesCadd : Supposons qu'un travail asynchrone génère une exception. Qui l'attrape ? Le code qui a lancé la tâche asynchrone n'est plus sur la pile - il peut même ne pas être sur la même pile que la tâche asynchrone. fil -- et les exceptions supposent que tous les blocs catch/finally sont sur la pile . Que faites-vous alors ? Nous stockons les informations relatives à l'exception dans la tâche, de sorte que vous puissiez les consulter ultérieurement. Mais si la méthode est nulle, il n'y a pas de tâche disponible pour le code de l'utilisateur. La manière dont nous traitons exactement cette situation a fait l'objet d'une certaine controverse et je ne me souviens pas pour l'instant de ce que nous avons décidé.

23voto

SLaks Points 391154

Au cas où l'appelant souhaite attendre la tâche ou ajouter une suite.

En fait, la seule raison de revenir void est si vous ne peut pas retour Task parce que vous écrivez un gestionnaire d'événements.

0 votes

Je pensais qu'il était également possible d'attendre des méthodes qui renvoient un type void.

1 votes

Non, vous ne pouvez pas. Si la méthode renvoie void vous n'avez aucun moyen d'accéder à la tâche qu'il génère. (En fait, je ne suis pas sûr qu'il génère même une tâche de Task du tout)

18voto

Stephen Cleary Points 91731

Méthodes renvoyant Task y Task<T> sont composables - ce qui signifie que vous pouvez await à l'intérieur d'un async méthode.

async les méthodes qui renvoient void ne sont pas composables, mais ils possèdent deux autres propriétés importantes :

  1. Ils peuvent être utilisés comme gestionnaires d'événements.
  2. Ils représentent une opération asynchrone de "niveau supérieur".

Le deuxième point est important lorsqu'il s'agit d'un contexte qui maintient un compter des opérations asynchrones en cours.

Le contexte ASP.NET est l'un de ces contextes ; si vous utilisez async Task sans les attendre d'une méthode asynchrone. void la requête ASP.NET se terminera trop tôt.

Un autre contexte est celui de la AsyncContext J'ai écrit pour les tests unitaires (disponibles aquí ) - le AsyncContext.Run suit le nombre d'opérations en cours et renvoie lorsqu'il est égal à zéro.

12voto

Killercam Points 9388

Type Task<T> est le type de base de la Task Parallel Library (TPL), il représente le concept de "travail qui va produire un résultat de type T à l'avenir". Le concept de "travail qui s'achèvera dans le futur mais qui ne donne aucun résultat" est représenté par le type de tâche non générique.

Précisément, comment le résultat du type T Le travail peut être confié à un autre processus sur la machine locale, à un autre groupe de travail, etc. Les tâches TPL sont généralement confiées à des threads de travail provenant d'un pool de threads dans le processus en cours, mais ce détail d'implémentation n'est pas fondamental pour le processus TPL. Task<T> mais plutôt un Task<T> peut représenter n'importe quelle opération à forte latence qui produit un T .

Sur la base de votre commentaire ci-dessus :

En await expression signifie "évaluer cette expression pour obtenir un objet représentant un travail qui produira à l'avenir un résultat". Inscrivez le reste de la méthode actuelle en tant que call back associé à la poursuite de cette tâche. Une fois que cette tâche est produite et que le call back est signé, immédiatement rendre le contrôle à mon interlocuteur". Cela s'oppose à un appel de méthode normal, qui signifie "n'oubliez pas ce que vous faites, exécutez cette méthode jusqu'à ce qu'elle soit complètement terminée, puis reprenez là où vous vous étiez arrêté, en connaissant maintenant le résultat de la méthode".


Edit : Je devrais citer l'article d'Eric Lippert dans le MSDN Magazine d'octobre 2011, car il m'a beaucoup aidé à comprendre ce genre de choses.

Pour plus d'informations et de pages blanches, voir aquí .

J'espère que cela vous sera utile.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X