2015-09-14 18 views
33

特にI/O操作を処理するときにMVCコントローラで非同期アクションが発生した場合、何が起こっているのか正確にはわかりません。のは、私がアップロードのアクションを持っているとしましょう:ASP.NET MVCでのasync/awaitの深い理解

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

    ... 
} 

私はこれらのことが起こるの基本的な手順です知ってから:

  1. 新しいスレッドがスレッドプールから覗くと、要求をincomming処理するために割り当てられています。

  2. コールがI/O操作の場合は、元のスレッドがプールに戻り、コントロールがいわゆるIOCP(入力出力完了ポート)に転送されます。私が理解できないことは、依頼がまだ生きていて、最終的に呼び出し側のクライアントが要求を完了するのを待つので、答えを待つことです。

私の質問は:誰が/いつ/どのように完全なブロックが発生するのですか?

注:私はThere Is No Threadブログの記事を見て、それがGUIアプリケーションのために理にかなっているが、このサーバー側のシナリオのために、私はそれを得ることはありません。本当に。

+1

あなたが探しているもの:regular [IHttpAsyncHandler](https://msdn.microsoft.com/en-us/library/system.web.ihttpasynchandler(v = vs.110).aspx)の'async' /' await'、なぜ 'IHttpAsyncHandler'がまったく動作するのでしょうか? –

+1

私はあなたが理解していない部分を理解しようとするのは苦労します。ハンドリングスレッドがプールに戻された場合、要求が停止する理由はありません。後で、おそらく別のスレッドで再開します。 BTW制御はIOCPに転送されない*。 –

答えて

21

これについて詳しく説明するネットには、いくつかの優れたリソースがあります。私はMSDN article that describes this at a high levelと書いた。

最後に呼び出し元のクライアントがリクエストを完了するのを待つため、私の理解していないのは、依頼がまだ生きていて、応答を待っている理由です。

ASP.NETランタイムがまだ完了していないため、まだ動作しています。リクエストの完了(レスポンスの送信による)は明示的なアクションです。それは要求がそれ自身で完了するようではありません。コントローラアクションがTask/Task<T>を返したとASP.NETが認識すると、そのタスクが完了するまで要求を完了しません。

私の質問は:who/when/howは完全なブロッキングが発生するまで待ちますか?

何も待っていません。

このように考える:ASP.NETは、処理中の現在の要求の集合を持っている。所定のリクエストについては、完了するとすぐにレスポンスが送信され、そのリクエストがコレクションから削除されます。

キーはスレッドのリクエストではなく、リクエストの集まりです。それらの要求のそれぞれは、任意の時点でそれに作用しているスレッドを持っていてもいなくてもよい。同期要求には常に1つのスレッド(同じスレッド)があります。非同期要求は、スレッドを持たない期間を持つことがあります。

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

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

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

+2

"キーとは、スレッドではなく要求の集まりであり、これらの要求のそれぞれは、ある時点でスレッドが動作しているかどうかに関係なく、常に1つのスレッド(同じスレッド)を持ちます。非同期要求にはスレッドがない時代があるかもしれません。 " – Sully

8

ASP.NETランタイムは、タスクが実行されていることを認識し、タスクが完了するまでHTTP応答の送信を遅延します。実際には、応答を生成するためにもTask.Resultの値が必要です。あなたのawaitがヒットしたときにあなたのコードとランタイムコードの両方がスタックから取得し、その時点で「何のスレッドがありません」

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

ランタイムは、基本的にこれを行います。 ContinueWithコールバックは要求を復活させ、応答を送信します。

10

コンパイラは、手の平を実行し、async \ awaitコードをコールバック付きTaskコードに変換します。最も単純なケースで: - Task秒とコールバックのちょうどたくさんだから、魔法はありません

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

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

のようなものに変更されます。より複雑なコードの場合、変換もより複雑になりますが、結果として得られるコードは、記述したものと論理的に等価になります。あなたが望むなら、ILSpy/Reflector/JustDecompileのいずれかを取って、コンパイルされたものを自分自身で見ることができます。

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

よくある誤解の1つは、asyncのすべてが別のスレッドを生成するということです。実際、それはほとんど反対です。 async Taskメソッドの長鎖の終わりには、通常、ある種の非同期IO操作(ディスクからの読み取りやネットワーク経由の通信など)を実行するメソッドがあります。これはWindows自体が行う魔法のようなものです。この操作の間、コードに関連するスレッドはまったくありません。事実上停止しています。ただし、操作が完了した後、Windowsがコールバックし、スレッドプールのスレッドが割り当てられて実行が継続されます。要求のHttpContextを保存するために必要なフレームワークコードがありますが、それだけです。

関連する問題