Comment Task <int> devient-il un int?

116

Nous avons cette méthode:

async Task<int> AccessTheWebAsync()
{ 
    HttpClient client = new HttpClient();

   Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

   // You can do work here that doesn't rely on the string from GetStringAsync.
   DoIndependentWork();

   string urlContents = await getStringTask;
   //The thing is that this returns an int to a method that has a return type of Task<int>
   return urlContents.Length;
}

Une conversion implicite se produit-elle entre Task<int>et int? Sinon, que se passe-t-il? Comment cela fonctionne-t-il?

Homme libre
la source
1
Continuez à lire . Je suppose que le compilateur s'occupe de cela en fonction du asyncmot clé.
D Stanley
1
@Freeman, regardez cette excellente explication: stackoverflow.com/a/4047607/280758
qehgt

Réponses:

171

Une conversion implicite se produit-elle entre Task <> et int?

Nan. C'est juste une partie de la façon dont async/ awaitfonctionne.

Toute méthode déclarée comme asyncdoit avoir un type de retour de:

  • void (éviter si possible)
  • Task (aucun résultat au-delà de la notification d'achèvement / d'échec)
  • Task<T>(pour un résultat logique de type Tde manière asynchrone)

Le compilateur effectue tout l'encapsulation appropriée. Le fait est que vous retournez de manière asynchroneurlContents.Length - vous ne pouvez pas simplement renvoyer la méthode int, car la méthode réelle retournera lorsqu'elle atteindra la première awaitexpression qui n'est pas déjà terminée. Donc, à la place, il retourne un Task<int>qui se terminera lorsque la méthode async elle-même sera terminée.

Notez que cela awaitfait le contraire - il déroule a Task<T>en une Tvaleur, c'est ainsi que cette ligne fonctionne:

string urlContents = await getStringTask;

... mais bien sûr, il le déballe de manière asynchrone, alors que le simple fait d'utiliser Resultle bloquerait jusqu'à ce que la tâche soit terminée. ( awaitpeut dérouler d'autres types qui implémentent le modèle attendu, mais Task<T>c'est celui que vous utiliserez probablement le plus souvent.)

Ce double emballage / déballage est ce qui permet à async d'être aussi composable. Par exemple, je pourrais écrire une autre méthode asynchrone qui appelle la vôtre et double le résultat:

public async Task<int> AccessTheWebAndDoubleAsync()
{
    var task = AccessTheWebAsync();
    int result = await task;
    return result * 2;
}

(Ou tout simplement return await AccessTheWebAsync() * 2;bien sûr.)

Jon Skeet
la source
3
peut-on offrir des détails sur son fonctionnement sous le capot, juste curieux.
Freeman
8
+1 Bonne réponse, comme toujours. Et pourquoi les écrivez-vous si vite?!
Felix K.
9
+1: Je viens de commencer à regarder dans async/ awaitet je trouve cela extrêmement peu intuitif. IMO, il devrait y avoir un mot-clé ou similaire à la returnpour que cela soit clair, par exemple return async result;(de la même manière que await result"déballe" le Tde Tast<T>).
dav_i
2
@JonSkeet Mais cela n'a pas de sens sans le await- avec T foo = someTaskT;vous obtiendrez "Impossible de convertir implicitement le type Task<T>en T" - de la même manière que je soutiens qu'il serait plus logique d'avoir un mot clé pour l'inverse (encapsulation Task<T>). Je suis tout à fait pour supprimer les peluches, mais dans ce cas, je pense que cela fournit une obfuscation inutile dans les asyncméthodes. (Évidemment, le point est discutable car les pouvoirs en place ont déjà parlé / codé!)
dav_i
2
@dav_i: L' affectation n'a pas de sens, mais le reste le fait. Et il y a des cas où la déclaration entière aurait du sens - même si cela pourrait ne pas être utile. Étant donné que la méthode est déjà déclarée async, je pense que cela suffit.
Jon Skeet
18

No nécessite la conversion de la tâche en int. Utilisez simplement le résultat de la tâche.

int taskResult = AccessTheWebAndDouble().Result;

public async Task<int> AccessTheWebAndDouble()
{
    int task = AccessTheWeb();
    return task;
}

Il retournera la valeur si disponible, sinon il retournera 0.

Aniket Sharma
la source
20
ce n'est pas ce que j'ai demandé.
Freeman
16
Cela ne répond pas à la question. Mais plus important encore, c'est un très mauvais conseil . Vous ne devriez presque jamais utiliser Result; cela peut provoquer des blocages! Considérez par exemple ce flux de travail: (1) Écrivez une note qui dit "tondre la pelouse". (2) Attendez que la pelouse soit tondue (3) Mangez un sandwich, (4) Faites ce qu'il dit sur la note ". Avec ce flux de travail, vous ne mangez jamais un sandwich ou ne tondez jamais la pelouse, car l'étape 2 est une attente synchrone sur quelque chose que vous ferez à l'avenir . Mais c'est le flux de travail que vous décrivez ici.
Eric Lippert
@EricLippert: Votre exemple n'est pas clair. Pouvez-vous expliquer comment Result peut introduire des blocages alors qu'attendre ne le fera pas?
CharithJ
3
Attendre signifie faire quelque chose pendant que vous attendez le résultat et que quelque chose peut inclure un travail pour calculer le résultat. Mais les attentes synchrones ne font rien pendant que vous attendez, ce qui signifie que vous pourriez empêcher l'exécution du travail.
Eric Lippert
1
@EricLippert. Cela aura-t-il le même problème? 'Task.Run (() => AccessTheWebAndDouble ()). Result;'
CharithJ