374 votes

Pourquoi utiliser async et renvoyer await, alors que vous pouvez renvoyer directement Task<T> ?

Y a-t-il tous scénario où l'on écrit une méthode comme celle-ci :

public async Task<SomeResult> DoSomethingAsync()
{
    // Some synchronous code might or might not be here... //
    return await DoAnotherThingAsync();
}

au lieu de cela :

public Task<SomeResult> DoSomethingAsync()
{
    // Some synchronous code might or might not be here... //
    return DoAnotherThingAsync();
}

aurait-elle un sens ?

Pourquoi utiliser return await alors que vous pouvez directement renvoyer Task<T> de l'intérieur DoAnotherThingAsync() invocation ?

Je vois un code avec return await dans tant d'endroits, je pense que j'ai peut-être raté quelque chose. Mais pour autant que je comprenne, ne pas utiliser les mots-clés async/await dans ce cas et renvoyer directement la tâche serait fonctionnellement équivalent. Pourquoi ajouter des frais généraux supplémentaires de await couche ?

7 votes

Je pense que la seule raison pour laquelle vous voyez cela est que les gens apprennent par imitation et qu'ils utilisent généralement (s'ils n'en ont pas besoin) la solution la plus simple qu'ils peuvent trouver. Donc les gens voient ce code, utilisent ce code, ils voient que ça marche et à partir de maintenant, pour eux, c'est la bonne façon de faire... Il ne sert à rien d'attendre dans ce cas.

21 votes

Il y a au moins une différence importante : propagation des exceptions .

1 votes

Je ne comprends pas non plus, je ne comprends pas du tout ce concept, il n'a aucun sens. D'après ce que j'ai appris, si une méthode a un type de retour, elle DOIT avoir un mot-clé return, n'est-ce pas la règle du langage C# ?

265voto

svick Points 81772

Il y a un cas sournois où return en méthode normale et return await en async se comporte différemment : lorsqu'elle est combinée avec la méthode using (ou, plus généralement, tout return await dans un try bloc).

Considérons ces deux versions d'une méthode :

Task<SomeResult> DoSomethingAsync()
{
    using (var foo = new Foo())
    {
        return foo.DoAnotherThingAsync();
    }
}

async Task<SomeResult> DoSomethingAsync()
{
    using (var foo = new Foo())
    {
        return await foo.DoAnotherThingAsync();
    }
}

La première méthode Dispose() les Foo dès que l'objet DoAnotherThingAsync() ce qui est probablement le cas bien avant qu'elle ne soit terminée. Cela signifie que la première version est probablement boguée (parce que Foo est éliminé trop tôt), tandis que la seconde version fonctionnera parfaitement.

5 votes

Pour être complet, dans le premier cas, vous devez renvoyer foo.DoAnotherThingAsync().ContinueWith(_ => foo.Dispose());

11 votes

@ghord Cela ne fonctionnerait pas, Dispose() retours void . Vous auriez besoin de quelque chose comme return foo.DoAnotherThingAsync().ContinueWith(t -> { foo.Dispose(); return t.Result; }); . Mais je ne sais pas pourquoi vous feriez cela alors que vous pouvez utiliser la deuxième option.

1 votes

@svick Vous avez raison, il faudrait plutôt dire { var task = DoAnotherThingAsync(); task.ContinueWith(_ => foo.Dispose()); return task; } . Le cas d'utilisation est assez simple : si vous êtes sur .NET 4.0 (comme la plupart), vous pouvez toujours écrire du code asynchrone de cette façon qui fonctionnera bien appelé à partir d'applications 4.5.

141voto

Stephen Cleary Points 91731

Si vous n'avez pas besoin de async (c'est-à-dire que vous pouvez renvoyer l'élément Task directement), alors n'utilisez pas async .

Il existe des situations où return await est utile, comme si vous aviez deux les opérations asynchrones à effectuer :

var intermediate = await FirstAsync();
return await SecondAwait(intermediate);

Pour en savoir plus async performance, voir l'article de Stephen Toub Article de MSDN y vidéo sur le sujet.

Mise à jour : J'ai écrit un article de blog qui donne beaucoup plus de détails.

20 votes

Pourriez-vous ajouter une explication sur la raison pour laquelle le await est utile dans le second cas ? Pourquoi ne pas faire return SecondAwait(intermediate); ?

2 votes

J'ai la même question que Matt. return SecondAwait(intermediate); atteindre l'objectif dans ce cas également ? Je pense que return await est redondant ici aussi...

33 votes

@MattSmith Cela ne compilerait pas. Si vous voulez utiliser await dans la première ligne, vous devez également l'utiliser dans la seconde.

30voto

Servy Points 93720

La seule raison pour laquelle vous voudriez le faire est qu'il existe une autre await dans le code précédent, ou si vous manipulez d'une manière ou d'une autre le résultat avant de le renvoyer. Cela peut également se produire par le biais d'une fonction try/catch qui modifie la façon dont les exceptions sont gérées. Si vous ne faites rien de tout cela, alors vous avez raison, il n'y a pas de raison d'ajouter la surcharge de rendre la méthode async .

4 votes

Comme pour la réponse de Stephen, je ne comprends pas pourquoi il serait nécessaire d'avoir recours à des mesures d'accompagnement. return await est nécessaire (au lieu de renvoyer simplement la tâche de l'invocation de l'enfant) même s'il y a une autre attente dans le code précédent . Pourriez-vous fournir une explication ?

12 votes

@TX_ Si vous souhaitez supprimer async alors comment attendriez-vous la première tâche ? Vous devez marquer la méthode comme async si vous souhaitez utiliser tous attend. Si la méthode est marquée comme async et vous avez un await plus tôt dans le code, alors vous devez await la deuxième opération asynchrone pour qu'elle soit du bon type. Si vous venez de supprimer await alors il ne compilerait pas car la valeur de retour ne serait pas du bon type. Puisque la méthode est async le résultat est toujours enveloppé dans une tâche.

1 votes

@Servy, je suppose que la question est de savoir pourquoi nous ferions cela. var result1 = await Task1Async(); return await Task2Async(result1) alors que nous pourrions nous contenter de var result1 = await Task1Async(); return Task2Async(result1) ? Je ne vois pas de raison, à part la gestion des exceptions éventuellement lancées par Task2Async dans ce champ d'application.

22voto

haimb Points 261

Si vous n'utilisez pas le retour attendu, vous risquez de gâcher votre trace de pile lors du débogage ou lorsqu'elle est imprimée dans les journaux en cas d'exception.

Lorsque vous renvoyez la tâche, la méthode a rempli son objectif et est sortie de la pile d'appels. Lorsque vous utilisez return await vous le laissez dans la pile d'appels.

Par exemple :

Appeler la pile lors de l'utilisation de await : A attend la tâche de B => B attend la tâche de C

Appeler la pile lorsque no en utilisant await : A attend la tâche de C, que B a renvoyée.

21voto

Andrew Arnott Points 35346

Voici un autre cas où vous devrez peut-être attendre le résultat :

async Task<IFoo> GetIFooAsync()
{
    return await GetFooAsync();
}

async Task<Foo> GetFooAsync()
{
    var foo = await CreateFooAsync();
    await foo.InitializeAsync();
    return foo;
}

En l'occurrence, GetIFooAsync() doit attendre le résultat de GetFooAsync car le type de T est différent entre les deux méthodes et Task<Foo> n'est pas directement attribuable à Task<IFoo> . Mais si vous attendez le résultat, il devient tout simplement Foo qui est directement attribuables à IFoo . La méthode asynchrone se contente alors de reconditionner le résultat à l'intérieur de Task<IFoo> et c'est parti.

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