なぜメソッドを「待ち」、すぐにその戻り値を調べるのでしょうか?


24

、このMSDNの記事、次のコード例は、(わずかに簡潔にするために編集された)が設けられています。

public async Task<ActionResult> Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

    Department department = await db.Departments.FindAsync(id);

    if (department == null)
    {
        return HttpNotFound();
    }

    return View(department);
}

このFindAsyncメソッドは、DepartmentIDでオブジェクトを取得し、を返しますTask<Department>。次に、部門がすぐにチェックされ、nullかどうかが確認されます。私が理解しているように、この方法でタスクの値を要求すると、待機中のメソッドから値が返されるまでコード実行ブロックされ、事実上これが同期呼び出しになります。

なぜこれをするのですか?Find(id)とにかくすぐにブロックする場合は、単に同期メソッドを呼び出す方が簡単ではありませんか?


実装に関連する可能性があります。... else return null;次に、メソッドが要求した部門を実際に見つけたことを確認する必要があります。
ジェレミー加藤

asp.netには何も表示されませんが、デストップアプリでは、この方法でUIを凍結することはありません
レミ

ここでは、設計者からのawait概念を説明するリンクがあります... msdn.microsoft.com/en-us/magazine/hh456401.aspx
ジョンRaynor

Awaitは、ASP.NETで考える価値があります。スレッドコンタクトの切り替えが遅くなるか、多くのスレッドスタックからのメモリ使用量が問題になる場合です。
イアン

回答:


24

私が理解しているように、この方法でタスクの値を要求すると、待機メソッドから値が返されるまでコードの実行がブロックされ、事実上これが同期呼び出しになります。

そうでもない。

呼び出すとawait db.Departments.FindAsync(id)、タスクが送信され、現在のスレッドが他の操作で使用するためにプールに返されます。実行の流れはブロックされます(department物事を正しく理解すれば、すぐに使用するかどうかに関係なく)、操作がマシン外で完了するのを待つ間、スレッド自体は他の物によって自由に使用できます(そしてイベントまたは完了ポートによって通知されます)。

呼び出したd.Departments.Find(id)場合、スレッドはほとんどの処理がDB上で行われているにもかかわらず、そこに座って応答を待ちます。

ディスクがバインドされると、CPUリソースを効果的に解放します。


2
しかし、私はすべてawaitが同じスレッドの継続としてメソッドの残りに署名することは例外だと思いました(いくつかの非同期メソッドは独自のスレッドをスピンアップします)、またはasync同じスレッドの継続としてメソッドに署名し、実行する残りのコード(ご覧のとおり、どのようにasync動作するのか明確ではありません)。あなたが説明していることは、より洗練された形式のように聞こえますThread.Sleep(untilReturnValueAvailable)
ロバートハーベイ

1
@RobertHarvey-継続として割り当てますが、タスク(およびその継続)を処理するために送信すると、実行するものは何もありません。(ConfigureAwaitiirc を介して)指定しない限り、同じスレッド上にあることは保証されません。
テラスティン

1
私の答えを参照してください...デフォルトでは、継続は元のスレッドにマーシャリングされます。
マイケルブラウン

2
私はここで私が見逃しているものを見ていると思います。これが何らかの利点を提供するためには、ASP.NET MVCフレームワークがawaitを呼び出す必要がありますpublic async Task<ActionResult> Details(int? id)。それ以外の場合、元の呼び出しはブロックされ、department == null解決を待機します。
ロバートハーベイ

2
@RobertHarvey 呼び出しが終了await ...する「戻る」までにFindAsync。それが待っています。コードが物事を待つようにするため、awaitと呼ばれます。(ただし、現在のスレッドを待機させるのと同じではないことに注意してください)
-user253751

17

タスクを待つ前に数行待つ方法がどの例にも示されていないことは本当に嫌です。このことを考慮。

Foo foo = await getFoo();
Bar bar = await getBar();

Console.WriteLine(“Do some other stuff to prepare.”);

doStuff(foo, bar);

これは、サンプルで推奨されている種類のコードであり、そのとおりです。これにはほとんど意味がありません。メインスレッドはUI入力への応答など、他のことを行うために解放されますが、async / awaitの真の力は、長時間実行される可能性のあるタスクが完了するのを待っている間、他のことを簡単に続けることができることです。上記のコードは「ブロック」され、Foo&Barが表示されるまで印刷行の実行を待機します。しかし待つ必要はありません。待機中に処理できます。

Task<Foo> foo = getFoo();
Task<Bar> bar = getBar();

Console.WriteLine(“Do some other stuff to prepare.”);

doStuff(await foo, await bar);

さて、コードを書き直したので、停止することなく、必要になるまで値を待ちます。私は常にこの種の機会に目を光らせています。待つ時間を賢くすることで、パフォーマンスが大幅に向上する可能性があります。最近、複数のコアがありますが、それらを使用することもあります。


1
うーん、それはコア/スレッドについてではなく、非同期呼び出しを適切に使用することについてです。
デュプリケータ

あなたは間違っていない@Deduplicator。それが、答えの中で「ブロック」を引用した理由です。スレッドに言及せずにこのようなことを話すのは難しいですが、それでも認めている間は、マルチスレッドが関与する場合としない場合があります。
ラバーダック

別のメソッド呼び出しの内部で待機することに注意することをお勧めします。コンパイラがこの待機を誤解し、本当に奇妙な例外が発生することがあります。すべてのasync / awaitは単なる構文シュガーであるため、生成されたコードがどのように見えるかをsharplab.ioで確認できます。私はこれを何度か観察しましたが、今では結果が必要な呼び出しの上の1行を待ちます...それらの頭痛は必要ありません。
evictednoise

「これにはほとんど意味がありません。」-これには多くの意味があります。「他のことをし続ける」ことがまったく同じ方法で適用できる場合はそれほど一般的ではありません。単にやりたいことがawaitあり、代わりにスレッドにまったく異なることをさせることがはるかに一般的です。
セバスチャンレッド

「これにはほとんど意味がない」と@SebastianRedlが言ったとき、実行、待機、実行、待機ではなく、明らかに両方のタスクを並行して実行できる場合を意味しました。これは、あなたが思っているよりもはるかに一般的です。もしあなたがあなたのコードベースを見渡せば、あなたは機会を見つけるでしょう。
RubberDuck

6

そのため、ここでは舞台裏でさらに多くのことが起こります。Async / Awaitは構文糖衣です。最初にFindAsync関数のシグネチャを見てください。タスクを返します。すでにキーワードの魔法を目にしているので、そのタスクを部門に展開します。

呼び出し元の関数はブロックしません。起こることは、部門への割り当てとawaitキーワードに続くすべてがクロージャーに入れられ、すべての意図と目的のためにTask.ContinueWithメソッドに渡されることです(FindAsync関数は自動的に別のスレッドで実行されます)。

もちろん、操作は元のスレッドにマーシャリングされるため(バックグラウンド操作を実行するときにUIと同期する必要がなくなります)、呼び出し関数がAsync(非同期的に呼び出される)同じことがスタックで発生します。

そのため、落とし穴なしで非同期操作の魔法を得ることができます。


1

いいえ、すぐには戻りません。awaitはメソッド呼び出しを非同期にします。FindAsyncが呼び出されると、Detailsメソッドは終了していないタスクを返します。FindAsyncが終了すると、その結果をdepartment変数に返し、残りのDetailsメソッドを再開します。


いいえ、ブロッキングはありません。asyncメソッドが既に終了するまで、department == nullには到達しません。
スティーブ

1
async await通常、新しいスレッドは作成されません。作成されたとしても、その部門の値がnullかどうかを確認するまで待つ必要があります。
ロバートハーヴェイ

2
したがって、基本的にあなたが言っているのは、これがまったく利益になるためには、への呼び出しpublic async Task<ActionResult> await編集
ロバートハーベイ

2
@RobertHarveyはい、あなたはアイデアを持っています。Async / Awaitは基本的にバイラルです。1つの関数を「待つ」場合、それを呼び出す関数も待つ必要があります。またはをawait混在させないでください。デッドロックが発生する可能性があります。通常、async / awaitチェーンは、主にイベントハンドラー、またはUI要素によって直接呼び出される関数に使用されるシグネチャを持つ関数で終了します。.Wait().Resultasync void
KChaloux

1
@Steve-" ...なので、独自のスレッドになります。 "いいえ、読み取りタスクはまだスレッドではなく、非同期は並列ではありません。これには、新しいスレッドが作成されないことを示す実際の出力が含まれます。
ToolmakerSteve

1

「非同期」をコントラクトのように考えるのが好きです。コントラクトは、「必要に応じて非同期で実行できますが、他の同期関数のように呼び出すこともできます」と言っています。

つまり、ある開発者が関数を作成し、いくつかの設計上の決定により、それらの関数を「非同期」として作成/マークすることになりました。関数の呼び出し元/消費者は、決定したとおりにそれらを使用する自由があります。あなたが言うように、関数呼び出しの直前にawaitを呼び出して待機することができます。これにより、同期関数のように処理できますが、必要であれば、

Task<Department> deptTask = db.Departments.FindAsync(id);

そして、たとえば、呼び出す関数の10行下に

Department d = await deptTask;

したがって、非同期関数として処理します。

それはあなた次第です。


1
「メッセージを非同期に処理する権利を留保し、これを使用して結果を取得する」というようなものです。
デュプリケータ

-1

「とにかくすぐにブロックする場合」、アンサーは「はい」です。高速応答が必要な場合にのみ、await / asyncが有効です。たとえば、UIスレッドは本当に非同期メソッドになり、UIスレッドは戻り、ボタンクリックをリッスンし続けますが、「await」以下のコードは他のスレッドによって実行され、最終的に結果を取得します。


1
この答えは、他の答えではできないことを提供しますか?
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.