ASP.NET Web API OperationCanceledException when browser cancels request

When a user loads a page, he creates one or more ajax requests that go to the ASP.NET Web API 2 controllers. If the user goes to another page before these ajax requests are completed, the requests will be canceled by the browser. Our ELMAH HttpModule logs two errors for each canceled request:

Error 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() 

Error 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) 

Looking at stacktrace, I see that the exception is thrown from here: https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Http.WebHost/HttpControllerHandler.cs#L413

My question is: how can I handle and ignore these exceptions?

It seems that outside the user code ...

Notes:

  • I am using ASP.NET Web API 2
  • The Web API endpoints are a combination of async and non-async methods.
  • No matter where I add the error log, I can not catch the exception in the user code
+95
iis asp.net-web-api task-parallel-library async-await
Mar 03 '14 at 21:15
source share
6 answers

This is a bug in ASP.NET Web API 2 and, unfortunately, I do not think that the workaround will always be successful. We fixed a mistake to fix it on our side.

Ultimately, the problem is that in this case we are returning the canceled task for ASP.NET, and ASP.NET treats the canceled task as an unhandled exception (it logs the problem in the application event log).

In the meantime, you can try something like the code below. It adds a top-level message handler that deletes content when the cancel token triggers. If the response has no content, the error should not be triggered. There is still a slight chance that this could happen, as the client can disconnect immediately after the message handler checks the cancellation token, but before the higher-level web API code performs the same check. But I think this will help in most cases.

David

 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; } } 
+66
Mar 24 '14 at 22:06
source share

When implementing an exception log for WebApi, it is recommended that you extend the System.Web.Http.ExceptionHandling.ExceptionLogger class instead of creating an ExceptionFilter. WebApi internals will not call the ExceptionLoggers logging method for canceled requests (however, exception filters will receive them). This is by design.

 HttpConfiguration.Services.Add(typeof(IExceptionLogger), myWebApiExceptionLogger); 
+9
Nov 18 '15 at 23:18
source share

You can try changing the default TPL exception handling behavior by default through web.config :

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

Then, in your web application, there is a static class (with a static constructor) that would handle AppDomain.UnhandledException .

However, it looks like this exception is actually handled somewhere inside the ASP.NET Web API runtime before you can even handle it with your code.

In this case, you should catch it as an exception from the 1st case, with AppDomain.CurrentDomain.FirstChanceException , here's how . I understand that this may not be what you are looking for.

+3
Mar 05 '14 at 0:18
source share

Here is another workaround. Just add the custom OWIN middleware at the beginning of the OWIN pipeline that catches OperationCanceledException :

 #if !DEBUG app.Use(async (ctx, next) => { try { await next(); } catch (OperationCanceledException) { } }); #endif 
+3
Dec 13 '16 at 14:39
source share

I sometimes get the same 2 exceptions in my Web API 2 application, but I can catch them using the Application_Error method in Global.asax.cs and use a common exception filter .

The funny thing is, however, I prefer not to catch these exceptions, because I always log all unhandled exceptions that could lead to the application crashing (these 2, however, are not related to me and apparently do not want to, or at least least shouldn't crash, but I could be wrong). I suspect that these errors occur due to some time-outs or an explicit rejection of the client, but I would expect them to be processed inside the ASP.NET framework and not propagated outside it as unhandled exceptions.

+2
Mar 06 '14 at 2:46
source share

We got the same exception, we tried to use the @dmatson workaround, but with it we got an exception. We have been doing this until recently. We noticed that some Windows logs are growing at an alarming rate.

Error files located in: C: \ Windows \ System32 \ LogFiles \ HTTPERR

Most errors were for "Timer_ConnectionIdle". I searched around and it seemed that although the call to web avi was completed, the connection was still maintained for two minutes after the initial connection.

Then I decided that we should try to close the connection in the response and see what happens.

I added response.Headers.ConnectionClose = true; in SendAsync MessageHandler and from what I can say that clients close connections, and we no longer experience this problem.

I know that this is not the best solution, but it works in our case. I am also sure that this is not what you would like to do if your API receives multiple calls from the same client to each other.

0
Feb 25 '16 at 20:44
source share



All Articles