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

Когда пользователь загружает страницу, он создает один или несколько запросов ajax, которые попадают в controllerы 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.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.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.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.d__0.MoveNext() 

Ошибка 2:

 System.OperationCanceledException: The operation was canceled. at System.Threading.CancellationToken.ThrowIfCancellationRequested() at System.Web.Http.WebHost.HttpControllerHandler.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.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.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 представляют собой сочетание асинхронных и неасинхронных методов.
  • Независимо от того, где я добавляю журнал ошибок, я не могу поймать исключение в коде пользователя
    • Global.asax Applicaiton_Error
    • TaskScheduler.UnobservedTaskException
    • ELMAH Ошибка фильтрации void ErrorLog_Filtering ( https://code.google.com/p/elmah/wiki/ErrorFiltering )

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

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

Тем временем вы можете попробовать что-то вроде кода ниже. Он добавляет обработчик сообщений верхнего уровня, который удаляет контент при срабатывании токена отмены. Если ответ не имеет содержания, ошибка не должна запускаться. По-прежнему существует небольшая возможность, так как клиент может отключиться сразу после того, как обработчик сообщения проверяет токен отмены, но до того, как код веб-API более высокого уровня выполнит ту же проверку. Но я думаю, что это поможет в большинстве случаев.

Дэвид

 config.MessageHandlers.Add(new CancelledTaskBugWorkaroundMessageHandler()); class CancelledTaskBugWorkaroundMessageHandler : DelegatingHandler { protected override async Task 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 рекомендуется расширить class 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 :

      

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

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

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

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

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

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

  1. OperationCanceledException
  2. TaskCanceledException

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

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

Я вижу два способа их решения:

  1. Просто игнорируя оба исключения в global.asax. Тогда возникает вопрос, можно ли вдруг игнорировать что-то важное?
  2. Выполняя дополнительную попытку / улов в обработчике (хотя это не пуленепробиваемый +, по-прежнему существует вероятность того, что TaskCanceledException которое мы игнорируем, будет тем, который мы хотим зарегистрировать.

 config.MessageHandlers.Add(new CancelledTaskBugWorkaroundMessageHandler()); class CancelledTaskBugWorkaroundMessageHandler : DelegatingHandler { protected override async Task 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. Однако, похоже, он не очень надежный.

PS Вот как я фильтрую эти ошибки:

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

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

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

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

  • ASP.NET MVC4 Async controller - зачем использовать?
  • Пример async / wait, который вызывает тупик
  • AspNetSynchronizationContext и ожидает продолжения в ASP.NET
  • Как выполнить задачу на фоне wpf, когда вы можете предоставить отчет и разрешить аннулирование?
  • Разница между возвратом и ожиданием задачи в асинхронном методе
  • HttpClient - задача была отменена?
  • В чем разница между задачей и streamом?
  • Is Task.Run считается плохой практикой в ​​веб-приложении ASP .NET MVC?
  • Вложенность в Parallel.ForEach
  • Где я могу найти версию streamа данных TPL для 4.0?
  • Если мой интерфейс должен вернуть Task, что является лучшим способом для реализации без операции?
  • Давайте будем гением компьютера.