浏览器取消请求时的ASP.NET Web API OperationCanceledException

时间:2014-03-03 21:15:20

标签: asp.net iis asp.net-web-api task-parallel-library async-await

当用户加载页面时,它会发出一个或多个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)

查看堆栈跟踪,我发现异常是从这里抛出的:https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Http.WebHost/HttpControllerHandler.cs#L413

我的问题是:我如何处理和忽略这些例外?

它似乎不在用户代码中......

注意:

  • 我正在使用ASP.NET Web API 2
  • Web API端点是异步和非异步方法的混合。
  • 无论我在哪里添加错误日志记录,我都无法在用户代码中捕获异常

8 个答案:

答案 0 :(得分:75)

这是ASP.NET Web API 2中的一个错误,不幸的是,我认为没有一种解决方法永远会成功。我们提交了bug来修复它。

最终,问题是我们在这种情况下将取消的任务返回给ASP.NET,并且ASP.NET将取消的任务视为未处理的异常(它在应用程序事件日志中记录问题)。

与此同时,您可以尝试类似下面的代码。它添加了一个顶级消息处理程序,可在取消令牌触发时删除内容。如果响应没有内容,则不应触发该错误。它可能发生的可能性很小,因为客户端可能会在消息处理程序检查取消令牌之后但在更高级别的Web 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;
    }
}

答案 1 :(得分:15)

为WebApi实现异常记录器时,建议扩展System.Web.Http.ExceptionHandling.ExceptionLogger类而不是创建ExceptionFilter。对于已取消的请求,WebApi内部不会调用ExceptionLoggers的Log方法(但是,异常过滤器将获取它们)。这是设计的。

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

答案 2 :(得分:10)

以下是此问题的其他解决方法。只需在OWIN管道的开头添加一个自定义OWIN中间件即可捕获OperationCanceledException

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

答案 3 :(得分:3)

您可以尝试更改default TPL task exception handling behaviorweb.config

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

然后在您的网络应用中有一个static类(带有static构造函数),它将处理AppDomain.UnhandledException

但是,在您甚至有机会使用代码处理它之前,似乎此异常实际上已在ASP.NET Web API运行时内部的某处处理

在这种情况下,您应该能够将AppDomain.CurrentDomain.FirstChanceExceptionhere is how作为第一次机会异常捕获。我知道这可能不是你想要的。

答案 4 :(得分:2)

我有时会在我的Web API 2应用程序中获得相同的2个异常,但是我可以使用Application_Error中的Global.asax.cs方法并使用通用exception filter来捕获它们。

有趣的是,我更喜欢不捕获这些异常,因为我总是记录所有可能导致应用程序崩溃的未处理异常(但是这两个对我来说无关紧要,显然不会或至少不应该不要崩溃,但我可能错了)。我怀疑这些错误是由于某些超时过期或客户端明确取消而出现的,但我原本希望它们在ASP.NET框架内处理,而不是作为未处理的异常在它之外传播。

答案 5 :(得分:1)

我们收到了相同的例外,我们尝试使用@ dmatson的解决方法,但我们仍然会遇到一些例外。我们直到最近才处理它。我们注意到一些Windows日志以惊人的速度增长。

错误文件位于: C:\ Windows \ System32下\ LogFiles文件\ HTTPERR

大多数错误全部用于&#34; Timer_ConnectionIdle&#34;。我四处搜索,似乎即使网络API呼叫完成,连接仍然保持原始连接两分钟。

然后我想我们应该尝试关闭响应中的连接,看看会发生什么。

我将response.Headers.ConnectionClose = true;添加到SendAsync MessageHandler,并且我可以告诉客户端正在关闭连接,我们不再遇到此问题。

我知道这不是最好的解决方案,但它适用于我们的情况。我还非常确定在性能方面,如果您的API从同一个客户端背对背地接到多个电话,那么您不想这样做。

答案 6 :(得分:1)

我发现了有关此错误的更多详细信息。有两种可能发生的例外情况:

  1. OperationCanceledException
  2. TaskCanceledException

如果在执行控制器中的代码时断开连接(或者围绕它的一些系统代码),则会发生第一个事件。如果在属性(例如AuthorizeAttribute)内执行时断开连接,则会发生第二种情况。

因此提供的workaround有助于部分缓解第一个异常,而对第二个异常则无济于事。在后一种情况下,TaskCanceledException发生在base.SendAsync调用期间,而不是将取消令牌设置为true。

我可以看到两种解决方法:

  1. 只需忽略global.asax中的两个异常。接下来的问题是,是否有可能突然忽略一些重要的事情呢?
  2. 在处理程序中进行额外的try / catch(尽管它不是防弹的+仍然有可能被我们忽略的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");
}

答案 7 :(得分:0)

OP提到了在ELMAH中忽略System.OperationCanceledException的愿望,甚至提供了正确方向的链接。自从最初的职位以来,ELMAH已经走了很长一段路,并且它提供了丰富的功能来准确执行OP的要求。请参阅this page(仍在完成中),其中概述了程序化和声明性(基于配置)方法。

我个人最喜欢的是直接在Web.config中进行的声明式方法。请遵循上面链接的指南,以了解如何为基于配置的ELMAH异常过滤设置Web.config。要专门过滤掉System.OperationCanceledException,可以这样使用is-type断言:

<configuration>
  ...
  <elmah>
  ...
    <errorFilter>
      <test>
        <or>
          ...
          <is-type binding="BaseException" type="System.OperationCanceledException" />
          ...
        </or>
      </test>
    </errorFilter>
  </elmah>
  ...
</configuration>