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 представляют собой сочетание методов async и non-async.
  • Независимо от того, где я добавляю журнал ошибок, я не могу поймать исключение в коде пользователя

Ответ 1

Это ошибка в 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 content in this case.
        if (cancellationToken.IsCancellationRequested)
        {
            return new HttpResponseMessage(HttpStatusCode.InternalServerError);
        }

        return response;
    }
}

Ответ 2

При реализации журнала исключения для WebApi рекомендуется расширить класс System.Web.Http.ExceptionHandling.ExceptionLogger вместо создания ExceptionFilter. Внутренние элементы WebApi не будут вызывать метод журнала ExceptionLoggers для отмененных запросов (однако фильтры исключений получат их). Это по дизайну.

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

Ответ 3

Вы можете попробовать изменить поведение обработки исключений по умолчанию TPL по умолчанию через web.config:

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

Затем в вашем веб-приложении есть класс static (с конструктором static), который обрабатывал бы AppDomain.UnhandledException.

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

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

Ответ 4

Вот еще один способ обхода проблемы. Просто добавьте пользовательское промежуточное ПО OWIN в начале конвейера OWIN, который ловит OperationCanceledException:

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

Ответ 5

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

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

Ответ 6

Мы получили одно и то же исключение, мы попытались использовать обходное решение @dmatson, но с ним все равно получилось какое-то исключение. Мы занимались этим до недавнего времени. Мы заметили, что некоторые журналы Windows растут с угрожающей скоростью.

Файлы ошибок, расположенные в: C:\Windows\System32\LogFiles\HTTPERR

Большинство ошибок были для "Timer_ConnectionIdle". Я искал вокруг, и казалось, что, хотя вызов веб-ави был завершен, соединение все еще сохранялось в течение двух минут после первоначального соединения.

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

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

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