web-dev-qa-db-fra.com

Quel est le but de "return wait" en C #?

Existe-t-il un scénario any dans lequel la méthode d'écriture ressemble à ceci:

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 un sens?

Pourquoi utiliser return await construct lorsque vous pouvez directement renvoyer Task<T> à partir de l'invocation DoAnotherThingAsync() intérieure?

Je vois du code avec return await dans tellement d'endroits, je pense que j'aurais dû manquer quelque chose. Mais pour autant que je sache, ne pas utiliser les mots-clés async/wait dans ce cas et renvoyer directement la tâche serait fonctionnellement équivalent. Pourquoi ajouter une surcharge supplémentaire de la couche await supplémentaire?

182
TX_

Il y a un cas sournois lorsque return dans la méthode normale et return await dans la méthode async se comportent différemment: lorsqu'il est combiné à using (ou plus généralement, tout return await dans un bloc try.

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 va Dispose() l'objet Foo dès que la méthode DoAnotherThingAsync() sera renvoyée, ce qui est probablement bien avant qu'elle ne se termine réellement. Cela signifie que la première version est probablement boguée (car Foo est supprimée trop tôt), alors que la deuxième version fonctionnera correctement.

142
svick

Si vous n’avez pas besoin de async (c’est-à-dire que vous pouvez renvoyer la Task directement), n’utilisez pas async.

return await est utile dans certaines situations, comme si vous avez deux opérations asynchrones à effectuer:

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

Pour plus d'informations sur les performances de async, voir l'article MSDN de Stephen Toub et vidéo sur le sujet.

Mise à jour: J'ai écrit un blog post qui va beaucoup plus en détail.

72
Stephen Cleary

La seule raison pour laquelle vous souhaitez le faire est s'il existe une autre variable await dans le code précédent ou si vous manipulez le résultat avant de le renvoyer. Cela pourrait également se produire par le biais d'un try/catch qui modifie le traitement des exceptions. Si vous ne le faites pas, vous avez raison, il n'y a aucune raison d'ajouter la surcharge liée à la méthode async.

23
Servy

Un autre cas où vous devrez peut-être attendre le résultat est celui-ci:

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

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

Dans ce cas, 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 affectable à Task<IFoo>. Mais si vous attendez le résultat, il devient simplement Foo qui est directement assignable à IFoo. Ensuite, la méthode asynchrone reconditionne simplement le résultat dans Task<IFoo> et c'est parti.

12
Andrew Arnott

Rendre la méthode "thunk" par ailleurs très simple async crée une machine à états asynchrone en mémoire, contrairement à la machine non asynchrone. Bien que cela puisse souvent amener les gens à utiliser la version non asynchrone, car elle est plus efficace (ce qui est vrai), cela signifie également qu'en cas de blocage, vous n'avez aucune preuve que cette méthode est impliquée dans la "pile retour/continuation" ce qui rend parfois plus difficile la compréhension du blocage. 

Alors oui, quand perf n'est pas critique (et ce n'est généralement pas le cas), je jetterai asynchrone sur toutes ces méthodes Thunk afin d'avoir la machine d'état asynchrone pour m'aider à diagnostiquer les blocages plus tard, et aussi Les méthodes thunk évoluent avec le temps, elles seront sûres de renvoyer des tâches erronées au lieu de lancer.

3
Andrew Arnott

Cela me trouble également et je pense que les réponses précédentes ont négligé votre question actuelle:

Pourquoi utiliser return wait construct quand vous pouvez directement renvoyer une tâche à partir de l'invocation interne de DoAnotherThingAsync ()?

Bien parfois, vous réellement voulez un Task<SomeType>, mais la plupart du temps vous voulez réellement une instance de SomeType, c'est-à-dire le résultat de la tâche.

À partir de votre code:

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

Une personne ne connaissant pas la syntaxe (moi, par exemple) pourrait penser que cette méthode devrait renvoyer un Task<SomeResult>, mais comme elle est marquée avec async, cela signifie que son type de retour réel est SomeResult. Si vous utilisez simplement return foo.DoAnotherThingAsync(), je retournerais une tâche, qui ne compilerait pas. La bonne façon est de renvoyer le résultat de la tâche, donc le return await.

2
heltonbiker

Si vous n'utilisez pas return, vous risquez de perdre votre trace de pile lors du débogage ou lors de l'impression dans les journaux des exceptions.

Lorsque vous renvoyez la tâche, la méthode remplit son objectif et ne fait plus partie de la pile d'appels . Lorsque vous utilisez return await, vous la laissez dans la pile d'appels.

Par exemple:

Pile d’appel lors de l’utilisation de wait: A en attente de la tâche de B => B en attente de la tâche de C

Pile d’appel lorsque non using attend: A en attente de la tâche de C, que B a renvoyée.

0
haimb