[asp.net] 브라우저가 요청을 취소 할 때 ASP.NET Web API OperationCanceledException

사용자가 페이지를로드하면 ASP.NET Web API 2 컨트롤러에 도달하는 하나 이상의 ajax 요청이 생성됩니다. 사용자가 다른 페이지로 이동하면 이러한 ajax 요청이 완료되기 전에 브라우저에서 요청을 취소합니다. 그런 다음 ELMAH HttpModule은 취소 된 각 요청에 대해 두 가지 오류를 기록합니다.

오류 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)

stacktrace를 살펴보면 https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Http.WebHost/HttpControllerHandler.cs# 에서 예외가 발생하고 있음을 알 수 있습니다. L413

내 질문은 : 이러한 예외를 어떻게 처리하고 무시할 수 있습니까?

사용자 코드 외부에있는 것 같습니다 …

노트:

  • ASP.NET Web API 2를 사용하고 있습니다.
  • Web API 끝점은 비동기 및 비 비동기 메서드의 혼합입니다.
  • 오류 로깅을 어디에 추가해도 사용자 코드에서 예외를 포착 할 수 없습니다.


답변

이것은 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;
    }
}


답변

WebApi에 대한 예외 로거를 구현할 때 System.Web.Http.ExceptionHandling.ExceptionLoggerExceptionFilter를 생성하는 대신 클래스 를 확장하는 것이 좋습니다 . WebApi 내부는 취소 된 요청에 대해 ExceptionLoggers의 Log 메서드를 호출하지 않습니다 (그러나 예외 필터는 요청을 가져옵니다). 이것은 의도적으로 설계된 것입니다.

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


답변

이 문제에 대한 다른 해결 방법은 다음과 같습니다. 다음을 포착하는 OWIN 파이프 라인의 시작 부분에 사용자 지정 OWIN 미들웨어를 추가하기 만하면됩니다 OperationCanceledException.

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


답변

다음을 통해 기본 TPL 작업 예외 처리 동작 을 변경해 볼 수 있습니다 web.config.

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

그런 다음 웹 앱에 생성자 가있는 static클래스를 가지고 .staticAppDomain.UnhandledException

그러나이 예외는 코드로 처리 할 기회도 갖기 전에 ASP.NET Web API 런타임 내부에서 실제로 처리되는 것으로 보입니다 .

이 경우에, 당신은과 더불어, 1 번째 예외로 잡을 수 있어야합니다 AppDomain.CurrentDomain.FirstChanceException, 여기에 방법이다 . 나는 이것이 당신이 찾고있는 것이 아닐 수도 있음을 이해합니다.


답변

Web API 2 응용 프로그램에서 때때로 동일한 2 개의 예외가 발생하지만 일반 예외 필터Application_Error 에서 메서드를 Global.asax.cs사용하여 예외 를 포착 할 수 있습니다 .

하지만 재미있는 점은 응용 프로그램을 충돌시킬 수있는 처리되지 않은 모든 예외를 항상 기록하기 때문에 이러한 예외를 포착하지 않는 것을 선호한다는 것입니다. 하지만 내가 틀릴 수 있습니다). 이러한 오류는 일부 시간 제한 만료 또는 클라이언트의 명시 적 취소로 인해 나타나는 것으로 생각되지만 ASP.NET 프레임 워크 내부에서 처리되고 처리되지 않은 예외로 전파되지 않을 것으로 예상했을 것입니다.


답변

이 오류에 대해 좀 더 자세한 내용을 찾았습니다. 발생할 수있는 두 가지 예외가 있습니다.

  1. OperationCanceledException
  2. TaskCanceledException

첫 번째는 컨트롤러의 코드가 실행되는 동안 연결이 끊어지면 발생합니다 (또는 그 주변의 일부 시스템 코드). 두 번째는 실행이 속성 (예 🙂 내에있는 동안 연결이 끊어지면 발생합니다 AuthorizeAttribute.

따라서 제공된 해결 방법 은 첫 번째 예외를 부분적으로 완화하는 데 도움이되며 두 번째 예외는 도움이되지 않습니다. 후자의 경우 취소 토큰이 true로 설정되는 대신 호출 자체 에서 TaskCanceledException발생 base.SendAsync합니다.

이 문제를 해결하는 두 가지 방법을 볼 수 있습니다.

  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");
}


답변

우리는 동일한 예외를 받고 있으며 @dmatson의 해결 방법을 사용하려고 시도했지만 여전히 예외가 발생했습니다. 우리는 최근까지 그것을 다루었습니다. 일부 Windows 로그가 놀라운 속도로 증가하는 것을 발견했습니다.

다음 위치에있는 오류 파일 : C : \ Windows \ System32 \ LogFiles \ HTTPERR

대부분의 오류는 모두 “Timer_ConnectionIdle”에 대한 것입니다. 주변을 검색 한 결과 웹 API 호출이 완료 되었음에도 불구하고 연결이 원래 연결을지나 2 분 동안 지속되는 것 같습니다.

그런 다음 응답에서 연결을 닫고 어떤 일이 발생하는지 확인해야한다고 생각했습니다.

response.Headers.ConnectionClose = true;SendAsync MessageHandler에 추가 했으며 클라이언트에게 연결이 닫히고 더 이상 문제가 발생하지 않는다는 것을 알 수 있습니다.

이것이 최선의 해결책은 아니지만 우리의 경우에는 작동합니다. 나는 또한 성능 측면에서 API가 동일한 클라이언트에서 연속적으로 여러 호출을받는 경우 수행하고 싶은 작업이 아니라고 확신합니다.