特にI/O操作を処理する場合、MVCコントローラーで非同期アクションを実行すると、舞台裏で何が起こっているのか正確にはわかりません。アップロードアクションがあるとします。
public async Task<ActionResult> Upload (HttpPostedFileBase file) {
....
await ReadFile(file);
...
}
私が知っていることから、これらは起こる基本的なステップです:
新しいスレッドがスレッドプールからピークされ、着信要求を処理するために割り当てられます。
待機がヒットすると、呼び出しがI/O操作である場合、元のスレッドはプールに戻り、制御はいわゆるIOCP(入力出力完了ポート)に転送されます。私が理解していないのは、リクエストがまだ生きており、最終的に呼び出しクライアントがリクエストの完了を待つため、回答を待つ理由です。
私の質問は、誰が/いつ/どのように完全なブロッキングが発生するのを待つのですか?
注:私はブログ投稿を見ましたスレッドがありません、GUIアプリケーションにとっては意味がありますが、このサーバー側にとってはシナリオはわかりません。本当に。
これを詳細に説明している「ネット」にはいくつかの優れたリソースがあります。 これを高レベルで説明する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は応答を書き出し、コレクションから要求を削除します。
内部では、コンパイラは洗練された処理を実行し、async
\await
コードをコールバック付きのTask
ベースのコードに変換します。最も単純な場合:
public async Task X()
{
A();
await B();
C();
}
次のように変更されます。
public Task X()
{
A();
return B().ContinueWith(()=>{ C(); })
}
魔法はありません-Task
sとコールバックがたくさんあります。より複雑なコードの場合、変換もより複雑になりますが、最終的に生成されるコードは、記述したものと論理的に同等になります。必要に応じて、ILSpy/Reflector/JustDecompileのいずれかを使用して、「内部」でコンパイルされたものを自分で確認できます。
ASP.NET MVCインフラストラクチャは、アクションメソッドが通常のものかTask
ベースのものかを認識し、その動作を順番に変更できるほどインテリジェントです。したがって、リクエストは「消失」しません。
よくある誤解の1つは、async
を持つすべてのものが別のスレッドを生成するということです。実際、それはほとんど正反対です。 async Task
メソッドの長いチェーンの終わりには、通常、非同期のIO操作(ディスクからの読み取りやネットワーク経由の通信など)を実行するメソッドがあります。この操作の実行中、コードに関連付けられたスレッドはまったくありません-効果的に停止します。ただし、操作の完了後、Windowsがコールバックし、スレッドプールからスレッドが割り当てられて続行しますリクエストのHttpContext
を保存するためのフレームワークコードが少しありますが、それだけです。
ASP.NETランタイムは、タスクとは何かを理解し、タスクが完了するまでHTTP応答の送信を遅らせます。実際、Task.Result
値は、応答を生成するためにも必要です。
ランタイムは基本的にこれを行います:
var t = Upload(...);
t.ContinueWith(_ => SendResponse(t));
await
がヒットすると、コードとランタイムコードの両方がスタックから外れ、その時点で「スレッドはありません」。 ContinueWith
コールバックはリクエストを復活させ、レスポンスを送信します。