174

あるどれかこのような書き方のシナリオ:

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層?


  • 私があなたがこれを見る唯一の理由は人々が模倣によって学びそして一般的に(彼らが必要としないのであれば)彼らが見つけることができる最も簡単な解決策を使うからです。それで、人々はそのコードを見て、そのコードを使用し、それがうまくいくのを見て、そして彼らのために、それを実行するための正しい方法であることを...彼らのために待つのは無駄です - Fabio Marcolini
  • 重要な違いが少なくとも1つあります。例外伝播。 - noseratio
  • 私はそれを理解しているのではなく、この概念全体を理解できないし、意味をなさない。メソッドが戻り値の型を持っているかどうか私が学んだことから、ITはreturnキーワードを持たなければなりません、それはC#言語の規則ではありませんか? - monstro

6 답변


136

卑劣なケースが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()メソッドが戻りますが、実際には完了するまでにかなり時間がかかります。これは最初のバージョンがおそらくバグがあることを意味します(なぜならFoo2番目のバージョンは正常に動作しますが、間に合わない)。


  • 完全を期すために、最初の場合は戻るべきですfoo.DoAnotherThingAsync().ContinueWith(_ => foo.Dispose()); - ghord
  • うまくいかなかった、Dispose()戻るvoid。あなたはのようなものが必要でしょうreturn foo.DoAnotherThingAsync().ContinueWith(t -> { foo.Dispose(); return t.Result; });。しかし、2番目のオプションを使用できるときに、なぜそうするのかわかりません。 - svick
  • @svickあなたはその通りです。{ var task = DoAnotherThingAsync(); task.ContinueWith(_ => foo.Dispose()); return task; }。ユースケースは非常に簡単です。あなたが(ほとんどのように)あなたが.NET 4.0を使っているなら、あなたはまだうまく非同期コードを書くことができます。 - ghord
  • @ghord .Net 4.0を使用していて、非同期コードを書きたい場合は、おそらく次のようにします。Microsoft.Bcl.Async。そしてあなたのコードはFoo返された後のみTask不必要に並行性が導入されるため、完了しません。 - svick
  • @svickあなたのコードはタスクも終了するまで待ちます。また、Microsoft.Bcl.AsyncはKB2468871への依存のために使用できず、.NET 4.0非同期コードベースを適切な4.5非同期コードと共に使用すると競合します。 - ghord

68

必要ない場合async(つまり、あなたはTask直接)、その後使用しないでくださいasync

いくつかの状況がありますreturn awaitあなたが持っているかのように、便利です行う非同期操作:

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

詳しくはasyncパフォーマンス、スティーブントゥーブのを参照してくださいMSDNの記事そしてビデオ話題になっている。

更新:私は書いたブログ記事それはもっと詳細に入ります。


  • その理由についての説明を追加してください。await2番目のケースで便利ですか?しないでくださいreturn SecondAwait(intermediate);? - Matt Smith
  • 私はマットと同じ質問をしていますが、そうではありませんreturn SecondAwait(intermediate);その場合も目標を達成する?おもうreturn awaitここでも冗長です... - TX_
  • @MattSmithコンパイルできませんでした。使いたい場合await1行目では、2行目でも使用する必要があります。 - svick
  • @ cateyes私は、「それらを並列にすることによってもたらされるオーバーヘッド」が何を意味するのかわからないが、asyncバージョンが使用しますもっと少なく同期バージョンよりリソース(スレッド)。 - svick
  • @TomLint実際にはコンパイルされません。の戻り型を仮定するSecondAwait&quot; CS4016:これは非同期メソッドであるため、戻り式はタイプ&#39; string&#39;である必要があります。 &#39;タスク&lt; string&gt;&#39;&quot;ではなく。 - svick

22

あなたがそれをしたいと思う唯一の理由は他に何かがあるかどうかですawait以前のコードで、または何らかの方法で結果を返す前に操作する場合。それが起こっているかもしれないもう一つの方法は、try/catchそれによって、例外の処理方法が変わります。あなたがそれのどれもしていないならそれからあなたは正しい、メソッドを作ることのオーバーヘッドを加える理由はないasync


  • Stephenの回答と同様に、なぜそうなるのかわからないreturn await(子呼び出しのタスクを単に返す代わりに)必要である他のコードが以前のコードで待っていたとしても。説明をお願いします。 - TX_
  • @TX_削除したい場合asyncそれでは、どのようにして最初のタスクを待ちますか?メソッドにマークを付ける必要がありますasync使いたい場合どれか待っています。メソッドがとしてマークされている場合asyncそしてあなたはawaitコードの初期の段階ではawaitそれが適切なタイプであるための2番目の非同期操作。削除したばかりの場合awaitそうすると、戻り値が適切な型にならないため、コンパイルできません。メソッドはasync結果は常にタスクにラップされています。 - Servy
  • @Noseratio 2つ試してください。最初のコンパイル2回目は行われません。エラーメッセージが問題を教えてくれます。適切なタイプを返すことができませんでした。ときにasyncタスクを返さない方法では、タスクの結果が返され、その結果はラップされます。 - Servy
  • @Servy、もちろん - あなたは正しいです。後者の場合は返却しますTask<Type>明示的にasync戻るように指示するType(コンパイラ自体はTask<Type>) - noseratio
  • @Itsik確かに、async明示的に継続を配線するための単なる構文上の糖です。あなたはしていません必要 async何かをするためですが、重要な非同期操作を実行するときには、劇的により扱いやすくなります。たとえば、指定したコードでは実際にエラーが発生することはありませんが、さらに複雑な状況で適切に実行すると、かなり困難になります。あなたは二度と必要 async、私が説明する状況は、それを使用する価値がある場所です。 - Servy

12

あなたが結果を待つ必要があるかもしれないもう一つのケースはこれです:

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

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

この場合、GetIFooAsync()の結果を待たなければならないGetFooAsyncタイプのためT2つの方法ではTask<Foo>に直接割り当てることはできませんTask<IFoo>。しかし、あなたが結果を待つならば、それはちょうどFooどっちですに直接割り当て可能IFoo。それからasyncメソッドは結果を内部に再パッケージ化するだけですTask<IFoo>そして離れて行きます。


3

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

つまり、perfが重要ではない(そして通常は重要ではない)場合は、これらすべてのさんくメソッドにasyncをスローして、後でハングしたときの診断に役立つ非同期ステートマシンを使用します。さんくメソッドは時間の経過とともに進化します、彼らはスローの代わりに失敗したタスクを返すようになるでしょう。


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()あなたはTaskを返すことになるでしょう、それはコンパイルされません。正しい方法はタスクの結果を返すことです。return await


  • &quot;実際の返品タイプ&quot;え?戻り値の型を変更しないで非同期または待機することはできません。あなたの例ではvar task = DoSomethingAsync();あなたにタスクを与えるのではなく、T - Shoe
  • @靴私はよく理解できていないかasync/await事私の理解では、Task task = DoSomethingAsync()その間Something something = await DoSomethingAsync()どちらもうまくいきます。 1つ目はあなたに適切なタスクを与えますが、2つ目はawaitキーワードは、あなたに与える結果完了後のタスクから例えば、Task task = DoSomethingAsync(); Something something = await task;。 - heltonbiker

リンクされた質問


関連する質問

最近の質問