web-dev-qa-db-ja.com

ASP.NET MVCでの非同期/待機の深い理解

特にI/O操作を処理する場合、MVCコントローラーで非同期アクションを実行すると、舞台裏で何が起こっているのか正確にはわかりません。アップロードアクションがあるとします。

public async Task<ActionResult> Upload (HttpPostedFileBase file) {
  ....
  await ReadFile(file);

  ...
}

私が知っていることから、これらは起こる基本的なステップです:

  1. 新しいスレッドがスレッドプールからピークされ、着信要求を処理するために割り当てられます。

  2. 待機がヒットすると、呼び出しがI/O操作である場合、元のスレッドはプールに戻り、制御はいわゆるIOCP(入力出力完了ポート)に転送されます。私が理解していないのは、リクエストがまだ生きており、最終的に呼び出しクライアントがリクエストの完了を待つため、回答を待つ理由です。

私の質問は、誰が/いつ/どのように完全なブロッキングが発生するのを待つのですか?

注:私はブログ投稿を見ましたスレッドがありません、GUIアプリケーションにとっては意味がありますが、このサーバー側にとってはシナリオはわかりません。本当に。

36
George Lica

これを詳細に説明している「ネット」にはいくつかの優れたリソースがあります。 これを高レベルで説明するMSDN記事 を書きました。

私が理解していないのは、リクエストがまだ生きており、最終的に呼び出しクライアントがリクエストの完了を待つため、答えを待つ理由です。

ASP.NETランタイムがまだ完了していないため、まだ生きています。 (応答を送信することにより)要求を完了することは明示的なアクションです。要求が単独で完了するわけではありません。 ASP.NETは、コントローラーアクションがTask/Task<T>を返すことを認識すると、そのタスクが完了するまで要求を完了しません。

私の質問は、誰が/いつ/どのように完全なブロッキングが発生するのを待つのですか?

何も待っていません。

このように考えてください。ASP.NETには、処理中の現在の要求のコレクションがあります。特定の要求については、完了するとすぐに応答が送信され、その要求はコレクションから削除されます。

重要なのは、スレッドではなくリクエストのコレクションであることです。これらの各要求には、どの時点でもスレッドが動作していてもいなくてもかまいません。同期要求には常に単一のスレッド(同じスレッド)があります。非同期リクエストには、スレッドがない期間がある場合があります。

注:私はこのスレッドを見ました: http://blog.stephencleary.com/2013/11/there-is-no-thread.html これはGUIアプリケーションにとっては意味がありますが、このサーバー側のシナリオにとっては意味がありますわかりません。

I/Oへのスレッドレスアプローチは、ASP.NETアプリでもGUIアプリとまったく同じように機能します。

最終的に、ファイルの書き込みが完了し、ReadFileから返されたタスクが(最終的に)完了します。この「タスクの完了」作業は、通常、スレッドプールスレッドで行われます。タスクが完了したため、Uploadアクションの実行が継続され、そのスレッドがリクエストコンテキストに入ります(つまり、そのリクエストを再度実行するスレッドが存在します)。 Uploadメソッドが完了すると、Uploadから返されたタスクが完了し、ASP.NETは応答を書き出し、コレクションから要求を削除します。

23
Stephen Cleary

内部では、コンパイラは洗練された処理を実行し、async\awaitコードをコールバック付きのTaskベースのコードに変換します。最も単純な場合:

public async Task X()
{
    A();
    await B();
    C();
}

次のように変更されます。

public Task X()
{
    A();
    return B().ContinueWith(()=>{ C(); })
}

魔法はありません-Tasksとコールバックがたくさんあります。より複雑なコードの場合、変換もより複雑になりますが、最終的に生成されるコードは、記述したものと論理的に同等になります。必要に応じて、ILSpy/Reflector/JustDecompileのいずれかを使用して、「内部」でコンパイルされたものを自分で確認できます。

ASP.NET MVCインフラストラクチャは、アクションメソッドが通常のものかTaskベースのものかを認識し、その動作を順番に変更できるほどインテリジェントです。したがって、リクエストは「消失」しません。

よくある誤解の1つは、asyncを持つすべてのものが別のスレッドを生成するということです。実際、それはほとんど正反対です。 async Taskメソッドの長いチェーンの終わりには、通常、非同期のIO操作(ディスクからの読み取りやネットワーク経由の通信など)を実行するメソッドがあります。この操作の実行中、コードに関連付けられたスレッドはまったくありません-効果的に停止します。ただし、操作の完了後、Windowsがコールバックし、スレッドプールからスレッドが割り当てられて続行しますリクエストのHttpContextを保存するためのフレームワークコードが少しありますが、それだけです。

11
Vilx-

ASP.NETランタイムは、タスクとは何かを理解し、タスクが完了するまでHTTP応答の送信を遅らせます。実際、Task.Result値は、応答を生成するためにも必要です。

ランタイムは基本的にこれを行います:

var t = Upload(...);
t.ContinueWith(_ => SendResponse(t));

awaitがヒットすると、コードとランタイムコードの両方がスタックから外れ、その時点で「スレッドはありません」。 ContinueWithコールバックはリクエストを復活させ、レスポンスを送信します。

10
usr