C#で「return await」の目的は何ですか?


251

このようなメソッドを書くシナリオはありますか

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

これの代わりに:

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

理にかなっていますか?

内部呼び出しからreturn await直接戻ることができるのに、なぜ構文を使用するのですか?Task<T>DoAnotherThingAsync()

return awaitたくさんの場所でコードが見つかるので、何かを見落としたかもしれません。しかし、私が理解している限り、この場合はasync / awaitキーワードを使用せず、タスクを直接返すことは機能的に同等です。なぜ追加awaitレイヤーの追加オーバーヘッドを追加するのですか?


2
私があなたがこれを見る唯一の理由は、人々が模倣によって学び、彼らが見つけることができる最も単純な解決策を一般に(必要でない場合)彼らが使うからだと思います。したがって、人々はそのコードを見て、そのコードを使用し、それが機能することを確認します。これから、彼らにとって、それは正しい方法です...その場合、待つのは
無駄です

7
少なくとも1つの重要な違いがあります。例外の伝播です。
noseratio 2014年

1
私もそれを理解していません。この概念全体をまったく理解できず、意味がありません。メソッドに戻り値の型がある場合、私が学んだことから、ITにはreturnキーワードが必要です(C#言語の規則ではないですか)。
モンストロ

@monstro OPの質問にはreturnステートメントがありますか?
David Klempfner、

回答:


189

場合1卑劣な場合がありreturn、通常の方法およびreturn awaitasyncと組み合わせた場合:この方法は、異なる動作using(または、より一般的には、任意return awaittryブロック)。

メソッドの次の2つのバージョンを検討してください。

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

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

最初のメソッドはDispose()、メソッドが戻るFooとすぐにオブジェクトを処理しDoAnotherThingAsync()ます。これは、実際に完了する前に行われる可能性があります。つまり、最初のバージョンはおそらくバグが多い(Foo廃棄が早すぎるため)が、2番目のバージョンは問題なく動作する。


4
完全をfoo.DoAnotherThingAsync().ContinueWith(_ => foo.Dispose());
期す

7
@ghordそれはうまくいきません、をDispose()返しますvoid。のようなものが必要になりますreturn foo.DoAnotherThingAsync().ContinueWith(t -> { foo.Dispose(); return t.Result; });。しかし、2番目のオプションを使用できるのに、なぜそうするのかわかりません。
2014

1
@svickそのとおりです{ var task = DoAnotherThingAsync(); task.ContinueWith(_ => foo.Dispose()); return task; }。ユースケースは非常に単純です。(ほとんどの場合と同様に).NET 4.0を使用している場合でも、この方法で非同期アプリを記述して、4.5アプリから呼び出すとうまく機能します。
ghord '23 / 09/14

2
@ghord .Net 4.0を使用していて、非同期コードを記述したい場合は、おそらくMicrosoft.Bcl.Asyncを使用する必要があります。そして、あなたのコードFooは返されたTask完了後にのみ破棄されますが、それは不必要に同時実行性を導入するためです。
2014

1
@svickコードもタスクが完了するまで待機します。また、Microsoft.Bcl.Asyncは、KB2468871に依存しているため使用できず、適切な4.5非同期コードで.NET 4.0非同期コードベースを使用すると競合します。
ghord

93

必要ない場合async(つまり、Task直接返すことができる場合)は使用しないでくださいasync

実行する2つの非同期操作return awaitがある場合など、いくつかの状況が役立ちます。

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

asyncパフォーマンスの詳細については、Stephen ToubのMSDNの記事ビデオを参照してください。

更新:より詳細に説明するブログ投稿を作成しました。


13
await2番目のケースでこれが役立つ理由について説明を追加していただけませんか?なぜしないのですreturn SecondAwait(intermediate);か?
マットスミス

2
マットと同じ質問がありますがreturn SecondAwait(intermediate);、その場合も目標を達成できませんか?return awaitここでも冗長だと思います...
TX_

23
@MattSmithコンパイルできません。await1行目で使用したい場合は、2行目でも使用する必要があります。
2013

2
@cateyes「並列化によって生じるオーバーヘッド」の意味はわかりませんが、このasyncバージョンは同期バージョンよりも少ないリソース(スレッド)を使用します。
2014

4
@TomLint 実際にはコンパイルされません。の戻り値の型SecondAwaitが `stringであるとすると、エラーメッセージは「CS4016:これは非同期メソッドであるため、戻り値の式は 'Task <string>'ではなく 'string'型である必要があります。」
2016年

23

これを実行する唯一の理由awaitは、以前のコードに他のコードが存在する場合、または結果を返す前に何らかの方法で結果を操作している場合です。これが発生している可能性のあるもう1つの方法は、try/catch例外の処理方法を変更するを使用することです。あなたがそれを何もしていないなら、あなたは正しいです、メソッドを作るオーバーヘッドを追加する理由はありませんasync


4
スティーブンの回答と同様に、以前のコードで他に待機しているものがある場合でもreturn await、(子の呼び出しのタスクを返すだけでなく)必要な理由がわかりません。説明していただけますか?
TX_ 2013

10
@TX_削除したい場合async、どのように最初のタスクを待ちますか?待機asyncを使用するかのようにメソッドをマークする必要があります。メソッドがとしてマークされasyncていて、awaitコードに以前のバージョンがある場合はawait、2番目の非同期操作を適切なタイプにする必要があります。削除しただけawaitでは、戻り値が適切なタイプではないため、コンパイルされません。メソッドはasync結果なので、常にタスクにラップされます。
2013

11
@Noseratio 2つ試してください。最初のコンパイル。2番目はしません。エラーメッセージは問題を教えてくれます。適切なタイプを返すことはありません。asyncメソッド内でタスクを返さない場合、タスクの結果が返され、ラップされます。
サービー2013

3
@Servy、もちろん-そうです。後者の場合Task<Type>、明示的asyncに戻りTypeますが、コンパイラー自体はに戻りTask<Type>ます。
noseratio 2013

3
@Itsik確かに、async継続を明示的に結び付けるための単なる構文上のシュガーです。何もする必要ありませんasync、重要な非同期操作を行う場合は、操作が大幅に簡単になります。たとえば、提供したコードは実際には意図したとおりにエラーを伝播せず、さらに複雑な状況で適切にエラーを伝達することは非常に困難になります。あなたが必要 asyncとすることは決してありません、私が説明する状況は、それを使用することの価値を付加するところです。
サービー2016

17

結果を待つ必要がある別のケースはこれです:

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

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

この場合、のタイプは2つのメソッド間で異なり、に直接割り当てることができないため、GetIFooAsync()は結果を待つ必要があります。しかし、結果を待つと、どちら直接に割り当て可能になるか決まります。次に、非同期メソッドは結果を内部に再パッケージ化するだけです。GetFooAsyncTTask<Foo>Task<IFoo>FooIFooTask<IFoo>


1
同意します、これは本当に厄介です-私は根本的な原因はそれTask<>が不変であると信じています。
StuartLC 2019

7

それ以外の場合は単純な「サンク」メソッドを非同期にすると、非同期ステートマシンがメモリ内に作成されますが、非同期ステートマシンは作成されません。非同期バージョンの方が効率的であるため(実際にそうです)、非同期バージョンの使用を人々に指摘することがよくありますが、ハングした場合、そのメソッドが「リターン/継続スタック」に関与している証拠はありません。これにより、ハングの理解が困難になる場合があります。

つまり、perfが重要ではない場合(そして通常は重要ではない)、これらのすべてのサンクメソッドに非同期をスローし、非同期状態マシンを使用して、後でハングを診断し、それらがサンクメソッドは時間の経過とともに進化するため、スローではなくフォールトタスクを確実に返します。


6

return awaitを使用しない場合は、デバッグ中または例外のログに出力されたときにスタックトレースを破壊する可能性があります。

タスクを返すと、メソッドは目的を果たし、コールスタックの外に出ます。使用return awaitすると、コールスタックに残されます。

例えば:

awaitを使用する場合の呼び出しスタック:AはBからのタスクを待機中=> BはCからのタスクを待機中

awaitを使用しない場合の呼び出しスタック:Aは、Bが返したCからのタスクを待機しています。


2

これも私を混乱させ、以前の答えはあなたの実際の質問を見落としたと私は感じています:

DoAnotherThingAsync()の内部呼び出しからTaskを直接返すことができるのに、なぜreturn await構文を使用するのですか?

まあ、実際にはが必要な場合もありますTask<SomeType>が、ほとんどの場合、実際にはのインスタンスSomeType、つまりタスクの結果が必要です。

あなたのコードから:

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

構文に不慣れな人(私など)は、このメソッドがを返すはずだと思っているかもしれませんTask<SomeResult>が、でマークされてasyncいるため、実際の戻り値の型はSomeResultです。だけを使用するとreturn foo.DoAnotherThingAsync()、コンパイルされないタスクが返されます。正しい方法はので、タスクの結果を返すことですreturn await


1
「実際の戻り値の型」。え?async / awaitは戻り値の型を変更しません。あなたの例でvar task = DoSomethingAsync();は、タスクではなくタスクを与えますT
Shoe

2
@Shoeよく分からなかったasync/awaitです。私の理解でTask task = DoSomethingAsync()は、Something something = await DoSomethingAsync()両方とも機能します。前者は適切なタスクを提供し、await後者はキーワードが原因で、完了後のタスクの結果を提供します。たとえば、私はできますTask task = DoSomethingAsync(); Something something = await task;
heltonbiker 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.