ASP.Netアプリケーションの非同期voidメソッドが次の例外を引き起こす理由を理解しようとしていますが、非同期タスクはそうではないようです:
System.InvalidOperationException: An asynchronous module or handler
completed while an asynchronous operation was still pending
私は.NETの非同期の世界に比較的新しいですが、次のすべてを含む多くの既存のリソースを介してこれを実行しようとしたように感じます。
これらのリソースから、通常はTaskを返し、非同期のvoidを回避することがベストプラクティスであることを理解しています。また、メソッドが呼び出されるとasync voidは未処理の操作の数を増やし、完了したら減ることを理解しています。これは、少なくとも私の質問に対する答えの一部のように聞こえます。しかし、私が行方不明になっているのは、Taskを返したときに何が起こるか、そしてそうすることで物事が「機能」するようになる理由です。
ここに私の質問をさらに説明するための不自然な例を示します。
public class HomeController : AsyncController
{
// This method will work fine
public async Task<ActionResult> ThisPageWillLoad()
{
// Do not await the task since it is meant to be fire and forget
var task = this.FireAndForgetTask();
return await Task.FromResult(this.View("Index"));
}
private async Task FireAndForgetTask()
{
var task = Task.Delay(TimeSpan.FromSeconds(3));
await task;
}
// This method will throw the following exception:
// System.InvalidOperationException: An asynchronous module or
// handler completed while an asynchronous operation was still pending
public async Task<ActionResult> ThisPageWillNotLoad()
{
// Obviously can't await a void method
this.FireAndForgetVoid();
return await Task.FromResult(this.View("Index"));
}
private async void FireAndForgetVoid()
{
var task = Task.Delay(TimeSpan.FromSeconds(3));
await task;
}
}
関連するメモで、非同期ボイドの私の理解が正しい場合、ASP.Netは実際にそれを忘れていないので、このシナリオで非同期ボイドを「火と忘れ」と考えるのは間違っていませんか?
Microsoftは、async
をASP.NETに取り込む際に、後方互換性の問題を可能な限り避けることを決定しました。そして、彼らはすべての「1つのASP.NET」にそれをもたらしたかったので、WinForms、MVC、WebAPI、SignalRなどのasync
サポート.
歴史的に、ASP.NETは、非同期コンポーネントがSynchronizationContext
に開始と完了を通知するイベントベースの非同期パターン(EAP)を介して、.NET 2.0以降のクリーンな非同期操作をサポートしてきました。 .NET 4.5では、このサポートに最初のかなり大きな変更が加えられ、ASP.NETの非同期型のコアが更新され、タスクベースの非同期パターン(TAP、つまりasync
)がより有効になりました。
それまでの間、異なるフレームワーク(WebForms、MVCなど)はすべて、後方互換性を優先して、そのコアと対話する独自の方法を開発しました。開発者を支援するために、ASP.NETのコアSynchronizationContext
は、表示されている例外を除いて拡張されました。多くの使用ミスをキャッチします。
WebFormsの世界では、RegisterAsyncTask
がありますが、多くの人が代わりにasync void
イベントハンドラを使用しています。したがって、ASP.NET SynchronizationContext
は、ページライフサイクルの適切なタイミングでasync void
を許可し、不適切なタイミングで使用すると、その例外が発生します。
MVC/WebAPI/SignalRの世界では、フレームワークはサービスとしてより構造化されています。したがって、彼らは非常に自然な方法でasync Task
を採用することができ、フレームワークは返されたTask
のみを扱う必要があります-非常にきれいな抽象化です。補足として、AsyncController
はもう必要ありません。 MVCは、Task
を返すという理由だけで非同期であることを認識しています。
ただし、Task
and を返そうとすると、async void
を使用しますが、これはサポートされていません。そして、それをサポートする理由はほとんどありません。とにかくそうすることを想定していないユーザーをサポートするだけでは、かなり複雑になります。 async void
は、MVCフレームワークを完全にバイパスして、コアASP.NET SynchronizationContext
に直接通知することに注意してください。 MVCフレームワークはTask
を待つ方法を理解していますが、async void
についても知らないため、ASP.NETコアに完了を返し、実際には完了。
これは、2つのシナリオで問題を引き起こす可能性があります。
async void
を使用するライブラリまたはその他を使用しようとしています。申し訳ありませんが、明白な事実は、ライブラリが壊れており、修正する必要があるということです。Task
にラップし、await
を適切に使用しています。 EAPコンポーネントはSynchronizationContext
と直接対話するため、これにより問題が発生する可能性があります。この場合、最良の解決策は、TAPを自然にサポートするように型を変更するか、TAP型(たとえば、HttpClient
ではなくWebClient
)に置き換えることです。それに失敗すると、TAP-over-EAPの代わりにTAP-over-APMを使用できます。どちらも実行できない場合は、TAP-over-EAPラッパーでTask.Run
を使用するだけで済みます。「火と忘れ」について:
私は個人的にこのフレーズをasync void
メソッドに使用することはありません。一つには、エラー処理のセマンティクスは、「火事と忘却」というフレーズにはほとんど確実に適合しません。私は冗談でasync void
メソッドを "fire and crash"と呼んでいます。真のasync
"fire and forget"メソッドは、返されるTask
を待つのではなく無視するasync Task
メソッドになります。
つまり、ASP.NETでは、リクエストから早期に戻りたくないことはほとんどありません(これは、 "fire and forget"が意味するものです)。この答えはすでに長すぎますが、本当に必要な場合は ASP.NET "fire and forget"をサポートするコードとともに、私のブログの問題の説明 があります。