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

128

En regardant divers exemples C # Async CTP, je vois certaines fonctions asynchrones qui renvoient voidet d'autres qui renvoient le non générique Task. Je peux voir pourquoi le renvoi d'un Task<MyType>est utile pour renvoyer des données à l'appelant lorsque l'opération asynchrone se termine, mais les fonctions que j'ai vues qui ont un type de Taskretour ne renvoient jamais de données. Pourquoi ne pas revenir void?

James Cadd
la source

Réponses:

214

Les réponses de SLaks et Killercam sont bonnes; Je pensais simplement 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 asyncpeut retourner void, Taskou Task<T>. Quelles sont les différences entre eux?

Une Task<T>méthode asynchrone de retour peut être attendue, et lorsque la tâche sera terminée, elle proposera un T.

Une Taskméthode asynchrone de retour peut être attendue, et lorsque la tâche est terminée, la poursuite de la tâche est planifiée pour s'exécuter.

UNE void méthode asynchrone de retour ne peut pas être attendue; c'est une méthode "feu et oublie". Cela fonctionne de manière asynchrone et vous n'avez aucun moyen de savoir quand cela est fait. C'est plus qu'un peu bizarre; comme le dit SLaks, vous ne feriez normalement cela que lorsque vous créez un gestionnaire d'événements asynchrone. L'événement se déclenche, le gestionnaire s'exécute; personne ne va "attendre" la tâche retournée par le gestionnaire d'événements parce que les gestionnaires d'événements ne retournent pas de tâches, et même s'ils l'ont fait, 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 awaitédité:

Quels types de méthodes peuvent être awaitédités? Une méthode de retour des annulations peut-elle être utilisée await?

Non, une méthode de retour de null ne peut pas être attendue. Le compilateur se traduit await M()par un appel à M().GetAwaiter(), où GetAwaiterpeut être une méthode d'instance ou une méthode d'extension. La valeur attendue doit être celle pour laquelle vous pouvez obtenir un serveur; clairement une méthode de retour de vide ne produit pas de valeur à partir de laquelle vous pouvez obtenir un serveur.

Task-les méthodes de retour peuvent produire des valeurs attendues. Nous prévoyons que des tiers voudront créer leurs propres implémentations d' Taskobjets similaires qui peuvent être attendus, et vous pourrez les attendre. Cependant, vous ne serez pas autorisé à déclarer des asyncméthodes qui renvoient autre chose que void, Taskou Task<T>.

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

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

Eric Lippert
la source
7
+1 Je pense que la seule chose qui manque est la différence dans la façon dont les exceptions sont traitées dans les méthodes asynchrones à retour de nullité.
João Angelo
10
@JamesCadd: Supposons qu'un travail asynchrone lève une exception. Qui l'attrape? Le code qui a démarré la tâche asynchrone n'est plus sur la pile - il peut même ne pas être sur le même thread - et les exceptions supposent que tous les blocs catch / finally sont sur la pile . Donc que fais-tu? Nous stockons les informations d'exception dans la tâche, afin que vous puissiez l'inspecter ultérieurement. Mais si la méthode est nulle, il n'y a pas de tâche disponible pour le code utilisateur. La manière exacte dont nous gérons cette situation a fait l’objet d’une certaine controverse et je ne me souviens pas pour le moment de ce sur quoi nous avons décidé.
Eric Lippert
8
J'ai en fait posé cette question à Stephen Toub à BUILD. Dans .NET 4.0 non observées, les exceptions non gérées dans les tâches finiraient par planter le processus une fois que TPL aurait détecté qu'elles n'étaient pas observées. Dans la version 4.5, ils ont changé le comportement par défaut afin que les exceptions non observées soient toujours signalées via l'événement TaskScheduler :: UnobservedTaskException, mais ne planteront plus le processus. Si vous voulez l'ancien comportement 4.0, vous pouvez réactiver avec <runtime> <ThrowUnobservedTaskExceptions enabled = "true" /> </runtime>. Très probablement, le changement a été fait précisément pour prendre en charge le feu et l'oubli pour les méthodes asynchrones vides.
Drew Marsh
4
async voidles méthodes lèvent leur exception sur le SynchronizationContextqui était actif au moment où elles ont commencé à s'exécuter. Ceci est similaire au comportement des gestionnaires d'événements (synchrones). @DrewMarsh: le UnobservedTaskExceptionparamètre d'exécution et ne s'applique qu'aux méthodes de tâche asynchrone "feu et oublie" , pas aux async voidméthodes.
Stephen Cleary
1
Lien de citation pour les informations sur la gestion des exceptions asynchrones: blogs.msdn.com/b/pfxteam/archive/2012/04/12/10293335.aspx#11
Luke Puplett
23

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

En fait, la seule raison de revenir voidest si vous ne pouvez pas revenir Taskcar vous écrivez un gestionnaire d'événements.

SLaks
la source
Je pensais qu'il était possible d'attendre des méthodes qui renvoient également un type vide - pourriez-vous élaborer un peu?
James Cadd
1
Non, tu ne peux pas. Si la méthode retourne void, vous n'avez aucun moyen d'accéder à la tâche qu'elle génère. (En fait, je ne sais pas si cela génère même un Taskdu tout)
SLaks
18

Les méthodes retournant Tasket Task<T>sont composables - ce qui signifie que vous pouvez awaitles utiliser à l'intérieur d'une asyncméthode.

asyncLes méthodes qui retournent voidne sont pas composables, mais elles ont 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 lorsque vous avez affaire à un contexte qui maintient un décompte d'opérations asynchrones en cours.

Le contexte ASP.NET est l'un de ces contextes; si vous utilisez des Taskméthodes asynchrones sans les attendre d'une asynchronevoid méthode , la requête ASP.NET sera exécutée trop tôt.

Un autre contexte est celui que AsyncContextj'ai écrit pour les tests unitaires (disponible ici ) - la AsyncContext.Runméthode suit le nombre d'opérations en cours et retourne quand il est à zéro.

Stephen Cleary
la source
12

Le type Task<T>est le type de bête de somme de la bibliothèque parallèle de tâches (TPL), il représente le concept de "un travail / travail qui va produire un résultat de type Tdans le futur". Le concept de «travail qui se terminera dans le futur mais ne renvoie aucun résultat» est représenté par le type de tâche non générique.

Précisément comment le résultat de type Tva être produit et les détails de mise en œuvre d'une tâche particulière; le travail peut être transféré vers un autre processus sur la machine locale, vers un autre thread, etc. Les tâches TPL sont généralement transférées vers des threads de travail à partir d'un pool de threads dans le processus actuel, mais ce détail d'implémentation n'est pas fondamental pour le Task<T>type; plutôt a Task<T>peut représenter toute opération à latence élevée qui produit un T.

Basé sur votre commentaire ci-dessus:

L' awaitexpression 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 comme rappel associé à la poursuite de cette tâche. Une fois cette tâche produite et le rappel est inscrit, retourne immédiatement le contrôle à mon interlocuteur ". Ceci est opposé / contrairement à un appel de méthode normal, qui signifie "souvenez-vous de ce que vous faites, exécutez cette méthode jusqu'à ce qu'elle soit complètement terminée, puis reprenez là où vous vous êtes arrêté, connaissant maintenant le résultat de la méthode".


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

Pour plus d'informations et de pages blanches, cliquez ici .

J'espère que cela vous aidera.

MoonKnight
la source