web-dev-qa-db-ja.com

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

ユーザーがページを読み込むと、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#L41

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

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

ノート:

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

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

最終的に、問題はキャンセルされたタスクをこの場合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;
    }
}
76
dmatson

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

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

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

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

デフォルトのTPLタスク例外処理動作 からweb.configを変更してみてください:

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

次に、AppDomain.UnhandledExceptionを処理するstaticクラス(staticコンストラクター)をWebアプリに作成します。

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

この場合、AppDomain.CurrentDomain.FirstChanceExceptionhere is how を使用して、1回目の例外としてキャッチできるはずです。これはあなたが探しているものではないかもしれないことを理解しています。

3
noseratio

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

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

2
Gabriel S.

同じ例外を受け取りました。@ dmatsonの回避策を使用しようとしましたが、まだいくつかの例外が発生していました。最近まで対処しました。一部のWindowsログが驚くべき速度で成長していることに気付きました。

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

ほとんどのエラーは「Timer_ConnectionIdle」に関するものです。私はあちこち検索しましたが、Web API呼び出しが終了しても、接続は元の接続から2分間持続したように見えました。

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

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

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

1
Michael Margala

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

  1. OperationCanceledException
  2. TaskCanceledException

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

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

私はこれらを解決する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;
    }
}

間違った例外を特定しようとする唯一の方法は、stacktraceにAsp.Netのものが含まれているかどうかを確認することです。ただし、非常に堅牢ではないようです。

追伸これは私がこれらのエラーを除外する方法です:

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