ASP.NET Web API OperationCanceledException when browser cancels the request
asp.net, asp.net-web-api, async-await, iis, task-parallel-library
Solution
This is a bug in ASP.NET Web API 2 and unfortunately, I don't think there's a workaround that will always succeed. We filed a bug to fix it on our side.
Ultimately, the problem is that we return a cancelled task to ASP.NET in this case, and ASP.NET treats a cancelled task like an unhandled exception (it logs the problem in the Application event log).
In the meantime, you could try something like the code below. It adds a top-level message handler that removes the content when the cancellation token fires. If the response has no content, the bug shouldn't be triggered. There's still a small possibility it could happen, because the client could disconnect right after the message handler checks the cancellation token but before the higher-level Web API code does the same check. But I think it 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's content in this case.
if (cancellationToken.IsCancellationRequested)
{
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
return response;
}
}
Problem
When a user loads a page, it makes one or more ajax requests, which hit ASP.NET Web API 2 controllers. If the user navigates to another page, before these ajax requests complete, the requests are canceled by the browser. Our ELMAH HttpModule then 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 the stacktrace, I see that the exception is being 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 appears to be outside of user code... Notes: - I am using ASP.NET Web API 2 - The Web API endpoints are a mix of async and non-async methods. - No matter where I add error logging, I am unable to catch the exception in user code - Global.asax `Applicaiton_Error` - `TaskScheduler.UnobservedTaskException` - ELMAH Error Filtering `void ErrorLog_Filtering` (https://code.google.com/p/elmah/wiki/ErrorFiltering)