ASP.NETはasync/awaitをご利用ください
5562 ワード
C#5.0はasync/awaitキーワードを導入し、非同期プログラミングモデルを簡略化することを目的とし、文法糖を投げ捨てるのがNet 4である.0のTask+ステータスマシン.非同期プログラミングでTaskを使うのは簡単ですが、新しい文法糖を発売した以上、試してみるのは避けられませんが、使用中は思ったほど単純ではありません.以下、ASP.NETアプリケーションの実際の使用過程のいくつかの総括は、異常なキャプチャ、デッドロック、アプリケーションのクラッシュを含めて、実際の使用過程で注意しないと穴に落ちる可能性があります.
asyncメソッドには、void、Task、Taskの3つの戻りタイプがあります.
async void
この方式で宣言する方法はcatchで異常をキャプチャできないため,以下のコードのtry,catchには卵用はない.
async Taskまたはasync Task
この2つの方法で宣言されるメソッド異常情報にはTask属性が含まれますが、tryでawaitを使用して待つ必要があります.
TaskScheduler.UnobservedTaskException
未取得のTask異常情報は、グローバルなTaskSchedulerを設定することにより得る.UnobservedTaskExceptionはGlobalでエラーログを記録します.asaxは次のコードを追加します.
非同期プログラミングは必然的にスレッドの使用についてであり、スレッドには同期コンテキストの概念があり、個人的にはスレッド同期コンテキストがasync/awaitが最も心配している問題だと考えている.既存のプロジェクト開発ではasync/awaitを使用しようとするかもしれませんが、古いコードは同期方式です.この場合、asyncとして宣言されたメソッドを呼び出すと、デッドロックやアプリケーションのクラッシュの問題が発生する可能性があります.
注意:コンソールプログラムと.NET Coreプログラムでは、コンテキストを同期する必要はありません.
デッドロック
以上のコードはデッドロックを完璧に実現しています.デフォルトでは、Wait()が完了していないTaskの場合、現在のスレッドコンテキストがキャプチャされ、Taskが完了したときにこのコンテキストリカバリメソッドの実行が使用されます.asyncメソッド内のawait実行が完了すると、呼び出し元スレッドが存在するコンテキスト実行メソッドの残りの部分を取得しようとしますが、このコンテキストにはasyncメソッドの完了を待つスレッドが含まれています.そして彼らは互いに相手を待っていて、それからなくて、そこで死んでいます.
デッドロックの問題に対する解決策はConfigureAwait(false)を増やすことである.
awaitが完了を待つと、スレッドプールコンテキストでasyncメソッドの残りの部分を実行しようとするため、デッドロックは存在しません.
アプリケーションのクラッシュ
テスト環境では、IISアプリケーションプールが常にクラッシュしていることがわかりますが、いったい何が原因ですか?当時、私たちはこの問題に対しても非常に愚かで、コードは明らかな欠点がないように見え、自分を騙そうとしたのは環境そのものの問題だったのではないでしょうか.しかし、実際にはコードに毒を仕掛けたのです.各種資料の閲覧とテストにより,同期コード呼び出し非同期コード,同期コンテキストによる問題と基本的に断定できる.
asyncメソッドが呼び出された場合.await待機を使用すると、現在のスレッドがすぐにループプールを解放され、スレッドのコンテキスト情報が保存されます.await(async voidのメソッドを使用していない場合、必然的にawaitを使用することはできません)が使用されていない場合、asyncメソッドを呼び出すと、コードは引き続き実行され、実行が完了すると現在のスレッドはループプールを解放され、スレッドのコンテキスト情報は保存されません.asyncの非同期タスクの実行が完了すると、スレッドプールから残りのコードの実行を続行するスレッドが取得され、当初の呼び出し元のスレッドのコンテキスト情報が取得されます(当初の呼び出し元のスレッドがループバックプールを解放していない場合、コンテキスト情報は取得できます).問題は、呼び出し元がawaitを使用せずにスレッドがループバックプールを解放していた場合、コンテキスト情報が保持されていないため取得できませんでした.この場合、例外がオブジェクト参照をオブジェクトに設定していないインスタンスが投げ出され、テストされた例外情報が毎回現れるとは限りません.理由はスレッドの解放に関係しています.呼び出し元が存在するスレッドのコンテキスト情報が存在する場合、例外は放出されません.例外エラーメッセージは次のとおりです.この例外は最終的にアプリケーションセットを停止させます.
以上の異常に対して、私たちはどのような方法で解決することができますか?依然としてConfigureAwait(false)であり、TaskにConfigureAwait(false)を追加します.この設定は、asyncの非同期タスクが完了した後、呼び出された元のスレッドのコンテキスト情報を読み取るのではなく、オンラインプールコンテキストでasyncメソッドを実行する残りの部分を表します.
同期と非同期コードをできるだけ混合しないで、非同期になるには最後まで非同期にします. configureAwait(false);
Do I need to use ConfigureAwait(false) all the way Don't Block on Async Code 同期メソッドasyncメソッドによるASPを呼び出す.NETアプリケーションクラッシュ
例外キャプチャ
asyncメソッドには、void、Task、Taskの3つの戻りタイプがあります.
async void
この方式で宣言する方法はcatchで異常をキャプチャできないため,以下のコードのtry,catchには卵用はない.
private static async void ThrowExceptionAsync()
{
await Task.Delay(1000);
throw new Exception(" ");
}
public static async void CatchAsyncVoidException()
{
try
{
ThrowExceptionAsync();
}
catch (Exception ex)
{
throw ex;
}
}
async Taskまたはasync Task
この2つの方法で宣言されるメソッド異常情報にはTask属性が含まれますが、tryでawaitを使用して待つ必要があります.
private static async Task ThrowExceptionAsync()
{
await Task.Delay(1000);
throw new Exception(" ");
}
public static async Task CatchAsyncTaskException()
{
try
{
await ThrowExceptionAsync();
}
catch (Exception ex)
{
throw ex;
}
}
TaskScheduler.UnobservedTaskException
未取得のTask異常情報は、グローバルなTaskSchedulerを設定することにより得る.UnobservedTaskExceptionはGlobalでエラーログを記録します.asaxは次のコードを追加します.
void Application_Start(object sender, EventArgs e)
{
//
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskExceptionException;
}
void TaskScheduler_UnobservedTaskExceptionException(object sender, UnobservedTaskExceptionEventArgs e)
{
if (e.Exception != null)
{
// do something
}
}
コンテキストの同期
非同期プログラミングは必然的にスレッドの使用についてであり、スレッドには同期コンテキストの概念があり、個人的にはスレッド同期コンテキストがasync/awaitが最も心配している問題だと考えている.既存のプロジェクト開発ではasync/awaitを使用しようとするかもしれませんが、古いコードは同期方式です.この場合、asyncとして宣言されたメソッドを呼び出すと、デッドロックやアプリケーションのクラッシュの問題が発生する可能性があります.
注意:コンソールプログラムと.NET Coreプログラムでは、コンテキストを同期する必要はありません.
デッドロック
private static async Task XXXAsync()
{
await Task.Delay(1000);
// some code
}
public static void Test()
{
var task = XXXAsync();
task.Wait();
}
以上のコードはデッドロックを完璧に実現しています.デフォルトでは、Wait()が完了していないTaskの場合、現在のスレッドコンテキストがキャプチャされ、Taskが完了したときにこのコンテキストリカバリメソッドの実行が使用されます.asyncメソッド内のawait実行が完了すると、呼び出し元スレッドが存在するコンテキスト実行メソッドの残りの部分を取得しようとしますが、このコンテキストにはasyncメソッドの完了を待つスレッドが含まれています.そして彼らは互いに相手を待っていて、それからなくて、そこで死んでいます.
デッドロックの問題に対する解決策はConfigureAwait(false)を増やすことである.
// await Task.Delay(1000);
await Task.Delay(1000).ConfigureAwait(false); //
awaitが完了を待つと、スレッドプールコンテキストでasyncメソッドの残りの部分を実行しようとするため、デッドロックは存在しません.
アプリケーションのクラッシュ
テスト環境では、IISアプリケーションプールが常にクラッシュしていることがわかりますが、いったい何が原因ですか?当時、私たちはこの問題に対しても非常に愚かで、コードは明らかな欠点がないように見え、自分を騙そうとしたのは環境そのものの問題だったのではないでしょうか.しかし、実際にはコードに毒を仕掛けたのです.各種資料の閲覧とテストにより,同期コード呼び出し非同期コード,同期コンテキストによる問題と基本的に断定できる.
asyncメソッドが呼び出された場合.await待機を使用すると、現在のスレッドがすぐにループプールを解放され、スレッドのコンテキスト情報が保存されます.await(async voidのメソッドを使用していない場合、必然的にawaitを使用することはできません)が使用されていない場合、asyncメソッドを呼び出すと、コードは引き続き実行され、実行が完了すると現在のスレッドはループプールを解放され、スレッドのコンテキスト情報は保存されません.asyncの非同期タスクの実行が完了すると、スレッドプールから残りのコードの実行を続行するスレッドが取得され、当初の呼び出し元のスレッドのコンテキスト情報が取得されます(当初の呼び出し元のスレッドがループバックプールを解放していない場合、コンテキスト情報は取得できます).問題は、呼び出し元がawaitを使用せずにスレッドがループバックプールを解放していた場合、コンテキスト情報が保持されていないため取得できませんでした.この場合、例外がオブジェクト参照をオブジェクトに設定していないインスタンスが投げ出され、テストされた例外情報が毎回現れるとは限りません.理由はスレッドの解放に関係しています.呼び出し元が存在するスレッドのコンテキスト情報が存在する場合、例外は放出されません.例外エラーメッセージは次のとおりです.この例外は最終的にアプリケーションセットを停止させます.
System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
System.Web.LegacyAspNetSynchronizationContext.CallCallbackPossiblyUnderLock(SendOrPostCallback callback, Object state)
System.Web.LegacyAspNetSynchronizationContext.CallCallback(SendOrPostCallback callback, Object state)
System.Web.LegacyAspNetSynchronizationContext.Post(SendOrPostCallback callback, Object state)
System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.PostAction(Object state)
System.Threading.Tasks.AwaitTaskContinuation.RunCallback(ContextCallback callback, Object state, Task& currentTask)
--- ---
System.Threading.Tasks.AwaitTaskContinuation.<>c.b__18_0(Object s)
System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
System.Threading.ThreadPoolWorkQueue.Dispatch()
System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
以上の異常に対して、私たちはどのような方法で解決することができますか?依然としてConfigureAwait(false)であり、TaskにConfigureAwait(false)を追加します.この設定は、asyncの非同期タスクが完了した後、呼び出された元のスレッドのコンテキスト情報を読み取るのではなく、オンラインプールコンテキストでasyncメソッドを実行する残りの部分を表します.
public static Task XXXAsync()
{
await Task.Run(() =>
{
// some code
}).ConfigureAwait(false);
}