ブラウザーがリクエストをキャンセルしたときのASP.NET Web API OperationCanceledException


119

ユーザーがページを読み込むと、ASP.NET Web API 2コントローラーにヒットする1つまたは複数のajax要求が行われます。ユーザーが別のページに移動した場合、これらのajaxリクエストが完了する前に、リクエストはブラウザーによってキャンセルされます。次に、ELMAH HttpModuleは、キャンセルされたリクエストごとに2つのエラーをログに記録します。

エラー1:

System.Threading.Tasks.TaskCanceledException: A task was canceled.
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Filters.AuthorizationFilterAttribute.<ExecuteAuthorizationFilterAsyncCore>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at System.Web.Http.Controllers.ExceptionFilterResult.<ExecuteAsync>d__0.MoveNext()

エラー2:

System.OperationCanceledException: The operation was canceled.
   at System.Threading.CancellationToken.ThrowIfCancellationRequested()
   at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.WebHost.HttpControllerHandler.<CopyResponseAsync>d__7.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.WebHost.HttpControllerHandler.<ProcessRequestAsyncCore>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.TaskAsyncHelper.EndTask(IAsyncResult ar)
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

スタックトレースを見ると、ここから例外がスローされていることがわかります:https : //github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Http.WebHost/HttpControllerHandler.cs# L413

私の質問は次のとおりです。これらの例外をどのように処理して無視できますか?

ユーザーコードの外にあるようです...

ノート:

  • ASP.NET Web API 2を使用しています
  • Web APIエンドポイントは、非同期メソッドと非非同期メソッドの混合です。
  • エラーログをどこに追加しても、ユーザーコードで例外をキャッチできません

1
現在のバージョンのKatanaライブラリでも同じ例外(TaskCanceledExceptionとOperationCanceledException)が見られます。
David McClelland

両方の例外が発生したときの詳細をいくつか見つけ、この回避策はどちらか一方に対してのみ機能することを理解しました。詳細は次のとおりです。stackoverflow.com
Ilya Chernomordik

回答:


78

これはASP.NET Web API 2のバグであり、残念ながら、常に成功する回避策はないと思います。私たちはそれを私たちの側で修正するためにバグを提出しました。

最終的に問題は、この場合、キャンセルされたタスクをASP.NETに返すことであり、ASP.NETはキャンセルされたタスクを未処理の例外のように扱います(アプリケーションイベントログに問題を記録します)。

それまでの間、以下のコードのようなものを試すことができます。キャンセルトークンが発生したときにコンテンツを削除するトップレベルのメッセージハンドラーを追加します。応答にコンテンツがない場合、バグは発生しません。メッセージハンドラーがキャンセルトークンをチェックした直後に、より高いレベルのWeb APIコードが同じチェックを行う前に、クライアントが切断される可能性があるため、発生する可能性はまだわずかです。しかし、それはほとんどの場合に役立つと思います。

デビッド

config.MessageHandlers.Add(new CancelledTaskBugWorkaroundMessageHandler());

class CancelledTaskBugWorkaroundMessageHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

        // Try to suppress response content when the cancellation token has fired; ASP.NET will log to the Application event log if there's content in this case.
        if (cancellationToken.IsCancellationRequested)
        {
            return new HttpResponseMessage(HttpStatusCode.InternalServerError);
        }

        return response;
    }
}

2
更新として、これはいくつかのリクエストをキャプチャします。ログにはまだかなりの数が表示されます。回避策をありがとう。修正を楽しみにしています。
ベイツウェストモアランド

2
@KiranChalla-5.2.2へのアップグレードにもまだこれらのエラーがあることを確認できます。
KnightFox 2015

2
それでもエラーが発生します。上記の提案、その他の手がかりを使用しました。
M2012

3
上記の推奨事項を試したところ、リクエストが渡される前にキャンセルされた場合でも例外が発生しましたSendAsyncF5APIにリクエストを送信するURLをブラウザーで押し続けると、これをシミュレートできます。この問題も解決しましたif (cancellationToken.IsCancellationRequested)への呼び出しの上にチェックを追加しSendAsyncます。ブラウザがリクエストをすばやくキャンセルしたときに例外が表示されなくなりました
seangwright

2
両方の例外が発生したときの詳細をいくつか見つけ、この回避策はどちらか一方に対してのみ機能することを理解しました。詳細は次のとおりです。stackoverflow.com
Ilya Chernomordik

17

WebApiの例外ロガーを実装する場合System.Web.Http.ExceptionHandling.ExceptionLogger、ExceptionFilterを作成するのではなく、クラスを拡張することをお勧めします。WebApi内部は、キャンセルされたリクエストに対してExceptionLoggersのLogメソッドを呼び出しません(ただし、例外フィルターはそれらを取得します)。これは仕様によるものです。

HttpConfiguration.Services.Add(typeof(IExceptionLogger), myWebApiExceptionLogger); 

このアプローチの問題は、それが例外ハンドラに送信されていないものの、エラーがまだGlobal.asaxのエラー処理に...イベントをポップアップすることであると思われる
イリヤChernomordik

14

この問題の別の回避策を次に示します。をキャッチするOWINパイプラインの先頭にカスタムOWINミドルウェアを追加するだけですOperationCanceledException

#if !DEBUG
app.Use(async (ctx, next) =>
{
    try
    {
        await next();
    }
    catch (OperationCanceledException)
    {
    }
});
#endif

2
私は主にOWINコンテキストからこのエラーを受け取っていましたが、これはより適切にターゲットを絞っています
NitinSingh

3

次の方法で、デフォルトのTPLタスクの例外処理動作を変更してみてくださいweb.config

<configuration> 
    <runtime> 
        <ThrowUnobservedTaskExceptions enabled="true"/> 
    </runtime> 
</configuration>

次に、Webアプリにstaticクラス(staticコンストラクター付き)を用意します。AppDomain.UnhandledException

ただし、この例外は実際には、コードで処理する前にASP.NET Web APIランタイム内のどこかで処理されているようです。

この場合は、あなたがして、第一のチャンス例外として、それをキャッチすることができるはずAppDomain.CurrentDomain.FirstChanceExceptionここにどのようにあります。これはあなたが探しているものではない可能性があることを理解しています。


1
どちらも私が例外を処理することを許可しませんでした。
ベイツウェストモアランド2014年

@BatesWestmoreland、ないFirstChanceExceptionですか?HTTPリクエスト間で持続する静的クラスでそれを処理しようとしましたか?
noseratio 2014年

2
私がキャッチして、これらの例外を無視するために解決しようとしている問題。AppDomain.UnhandledExceptionまたはを使用AppDomain.CurrentDomain.FirstChanceExceptionすると、例外を検査できますが、キャッチして無視することはできません。これらの例外のいずれかをこれらのアプローチのいずれかを使用して処理されるものとしてマークする方法がありませんでした。私が間違っていたら訂正してください。
ベイツウェストモアランド

2

Web API 2アプリケーションで同じ2つの例外が発生することがありますが、Application_ErrorメソッドGlobal.asax.csの汎用例外フィルターを使用してそれらをキャッチできます

ただし、面白いのは、これらの例外をキャッチしないことです。これは、アプリケーションをクラッシュさせる可能性があるすべての未処理の例外を常にログに記録するためです(ただし、これら2つは私には無関係で、クラッシュしないか、少なくともクラッシュしないはずです)。それ、しかし私は間違っているかもしれません)。タイムアウトの期限切れやクライアントからの明示的なキャンセルが原因でこれらのエラーが表示されると思いますが、ASP.NETフレームワーク内で処理され、その外に未処理の例外として伝播されないことが期待されました。


私の場合、ユーザーが新しいURLに移動するとブラウザーが要求をキャンセルするため、これらの例外が発生します。
ベイツウェストモアランド

1
そうですか。私の場合、リクエストはWinHTTPブラウザからではなく、API を通じて発行されます。
Gabriel S.

2

このエラーについてもう少し詳しく知りました。発生する可能性のある2つの可能な例外があります。

  1. OperationCanceledException
  2. TaskCanceledException

最初の問題は、コントローラー内のコードの実行中に接続が切断された場合に発生します(またはその周りのシステムコードも同様です)。2番目は、実行が属性内にあるときに接続がドロップされた場合に発生します(たとえば、AuthorizeAttribute

したがって、提供された回避策は、最初の例外を部分的に軽減するのに役立ちますが、2番目の例外には役立ちません。後者の場合、キャンセルトークンがtrueに設定されるのではなく、呼び出し自体のTaskCanceledException間に発生base.SendAsyncします。

これらを解決する方法は2つあります。

  1. global.asaxで両方の例外を無視するだけです。次に、代わりに重要な何かを突然無視することが可能かどうかという質問が来ますか?
  2. ハンドラーで追加のtry / catchを実行します(これは完全なものではありませんTaskCanceledExceptionが、ログに記録したいものは無視される可能性があります。

config.MessageHandlers.Add(new CancelledTaskBugWorkaroundMessageHandler());

class CancelledTaskBugWorkaroundMessageHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        try
        {
            HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

            // Try to suppress response content when the cancellation token has fired; ASP.NET will log to the Application event log if there's content in this case.
            if (cancellationToken.IsCancellationRequested)
            {
                return new HttpResponseMessage(HttpStatusCode.InternalServerError);
            }
        }
        catch (TaskCancellationException)
        {
            // Ignore
        }

        return response;
    }
}

誤った例外を特定しようとする唯一の方法は、スタックトレースにAsp.Netの要素が含まれているかどうかを確認することです。とはいえ、あまり堅牢ではないようです。

PSこれは私がこれらのエラーを取り除く方法です:

private static bool IsAspNetBugException(Exception exception)
{
    return
        (exception is TaskCanceledException || exception is OperationCanceledException) 
        &&
        exception.StackTrace.Contains("System.Web.HttpApplication.ExecuteStep");
}

1
提案されたコードではresponse、tryの内部で変数を作成し、tryの外部でそれを返します。それは可能な仕事ではありませんか?また、IsAspNetBugExceptionはどこで使用しますか?
Schoof

1
いいえ、機能しません。もちろん、try / catchブロックの外側で宣言し、完了したタスクなどで初期化する必要があるだけです。これは、とにかく完全ではないソリューションの例にすぎません。他の質問については、Global.Asax OnErrorハンドラーで使用します。メッセージを記録するためにそれを使用しない場合でも、とにかく心配する必要はありません。そうする場合、これはシステムから「エラー以外」を除外する方法の例です。
Ilya Chernomordik

1

同じ例外が発生しました。@ dmatsonの回避策を使用しようとしましたが、それでも例外が発生します。最近まで対応してきました。一部のWindowsログが驚くべき速度で増加していることに気付きました。

C:\ Windows \ System32 \ LogFiles \ HTTPERRにあるエラーファイル

エラーのほとんどはすべて「Timer_ConnectionIdle」に関するものでした。周りを検索したところ、Web API呼び出しが終了したにもかかわらず、接続は元の接続を2分間超えたままでした。

次に、応答で接続を閉じて、何が起こるかを確認する必要があると考えました。

追加した response.Headers.ConnectionClose = true;SendAsync MessageHandlerしましたが、クライアントから接続が閉じられていることがわかり、問題はもう発生していません。

これが最善の解決策ではないことはわかっていますが、私たちの場合はうまくいきます。また、パフォーマンスに関しては、APIが同じクライアントから連続して複数の呼び出しを取得している場合、これは実行したくないものだと私は確信しています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.