ASP.NET Web API OperationCanceledException когда браузер отменяет запрос



когда пользователь загружает страницу, он делает один или несколько запросов ajax, которые попадают ASP.NET Web API 2 контроллеры. Если пользователь переходит на другую страницу, до завершения этих запросов 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 представляют собой сочетание асинхронных и неасинхронных методов.

  • независимо от того, где я добавляю журнал ошибок, я не удается поймать исключение в коде пользователя


853   7  

7 ответов:

это ошибка в ASP.NET Web API 2 и, к сожалению, я не думаю, что есть обходной путь, который всегда будет успешным. Мы подали заявление ошибка исправить на нашей стороне.

в конечном счете, проблема заключается в том, что мы возвращаем отмененную задачу ASP.NET в этом случае, и ASP.NET обрабатывает отмененную задачу как необработанное исключение (регистрирует проблему в журнале событий приложения).

в то же время, вы можете попробовать что-то вроде кода ниже. Он добавляет верхний уровень обработчик сообщений, который удаляет содержимое при срабатывании маркера отмены. Если ответ не содержит содержимого, ошибка не должна быть вызвана. Есть еще небольшая вероятность, что это может произойти, потому что клиент может отключиться сразу после того, как обработчик сообщений проверит токен отмены, но до того, как код веб-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.ExceptionLogger класс, а не создание ExceptionFilter. Внутренние компоненты WebApi не будут вызывать метод журнала ExceptionLoggers для отмененных запросов (однако фильтры исключений получат их). Это сделано специально.

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 класса (с static конструктор) в вашем веб-приложении, которое будет обрабатывать AppDomain.UnhandledException.

однако, похоже, что это исключение на самом деле обрабатывается где-то внутри ASP.NET Web API runtime, прежде чем у вас даже есть шанс справиться с этим с помощью вашего кода.

в этом случае, вы должны быть возможность поймать его как исключение 1-го шанса, с AppDomain.CurrentDomain.FirstChanceException,вот как. Я понимаю, что это может быть не то, что вы ищете.

Я иногда получаю те же 2 исключения в моем приложении Web API 2, однако я могу поймать их с помощью Application_Error метод Global.asax.cs и, используя универсальный фильтр исключений.

самое смешное, однако, я предпочитаю не ловить эти исключения, потому что я всегда регистрирую все необработанные исключения, которые могут привести к сбою приложения (эти 2, однако, не имеют отношения ко мне и, по-видимому, не должны или, по крайней мере, не должны разбивать его, но я могу ошибаться). Я подозреваю эти ошибки появляется из-за истечения некоторого времени ожидания или явной отмены от клиента, но я ожидал бы, что они будут обработаны внутри ASP.NET фреймворк и не распространяется за его пределы как необработанные исключения.

я нашел немного больше информации об этой ошибке. Есть 2 возможных исключения, которые могут произойти:

  1. OperationCanceledException
  2. TaskCanceledException

первый происходит, если соединение отбрасывается во время выполнения вашего кода в контроллере (или, возможно, какой-то системный код вокруг этого). В то время как второй будет, если связь обрывается во время выполнения внутри атрибута (например,AuthorizeAttribute).

поэтому указанное решение помогает частично смягчить первое исключение, он ничего не делает, чтобы помочь со вторым. В последнем случае TaskCanceledException происходит во время base.SendAsync назовите себя скорее тем, что токен отмены установлен в true.

Я вижу два пути решения:

  1. просто игнорируя оба исключения в глобальном.асакс. Тогда возникает вопрос, Можно ли внезапно игнорировать что-то важное вместо этого?
  2. делаешь еще попробовать/ловить в обработчике (хотя это не пуленепробиваемый + есть еще возможность, что 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 чепуха. Не кажется очень надежной.

P. S. Это, как мне фильтровать эти ошибки:

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 был завершен, соединение все еще сохранялось для через две минуты после первоначального соединения.

затем я решил, что мы должны попытаться закрыть соединение в ответе и посмотреть, что произойдет.

добавил response.Headers.ConnectionClose = true; в SendAsync MessageHandler и из того, что я могу сказать, клиенты закрывают соединения, и мы больше не испытываем проблемы.

Я знаю, что это не лучшее решение, но это работает в нашем случае. Я также уверен, что с точки зрения производительности это не то, что вы хотели бы сделать, если ваш API получает несколько вызовов от одного и того же клиента спина к спине.

Comments

    Ничего не найдено.