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


119

当用户加载页面时,它发出一个或多个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端点是异步和非异步方法的混合。
  • 无论我在哪里添加错误日志记录,我都无法捕获用户代码中的异常

1
当前版本的Katana库中,我们也看到了相同的异常(TaskCanceledException和OperationCanceledException)。
David McClelland'17年

我发现了有关何时都发生异常的更多详细信息,并发现此变通办法仅适用于其中之一。这里还有一些细节:stackoverflow.com/questions/22157596/...
伊利亚Chernomordik

Answers:


78

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

最终,问题在于,在这种情况下,我们将取消的任务返回给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;
    }
}

2
作为更新,它确实捕获了一些请求。我们仍然在日志中看到很多。感谢您的解决方法。期待修复。
贝茨韦斯特摩兰2014年

2
@KiranChalla-我可以确认升级到5.2.2仍然有这些错误。
KnightFox

2
仍然我得到错误。我已经使用了上面的建议,还有其他任何线索。
M2012

3
当我尝试上述建议时,即使在请求被传递之前,我仍然收到异常消息SendAsync(您可以通过F5在浏览器中按住向您的Api发出请求的url 来模拟此情况。我也通过解决了此问题添加if (cancellationToken.IsCancellationRequested)调用上述检查SendAsync现在异常不再出现在浏览器迅速取消请求。
seangwright

2
我发现了有关何时都发生异常的更多详细信息,并发现此变通办法仅适用于其中之一。以下是一些详细信息:stackoverflow.com/a/51514604/1671558
Ilya Chernomordik,

17

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

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

这种方法的问题似乎是该错误仍然会在Global.asax错误处理中弹出...事件,尽管它没有发送给异常处理程序
Ilya Chernomordik,

14

这是此问题的另一种解决方法。只需在OWIN管道的开头添加一个自定义的OWIN中间件即可OperationCanceledException

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

2
我主要是从OWIN上下文中得到此错误,并且此错误的针对性更好
NitinSingh

3

你可以尝试改变默认的TPL任务异常处理行为通过web.config

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

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

但是,似乎该异常实际上是在ASP.NET Web API运行时内部某个地方处理的,甚至没有机会使用您的代码对其进行处理。

在这种情况下,你应该能够赶上它作为一个第一次机会异常,有AppDomain.CurrentDomain.FirstChanceException这里是如何。我了解这可能不是您想要的。


1
这些都不允许我处理异常。
贝茨威斯特摩兰

@BatesWestmoreland,甚至不FirstChanceException?您是否尝试过使用在HTTP请求中持久存在的静态类来处理它?
noseratio 2014年

2
我试图解决该问题以解决并忽略这些异常。使用AppDomain.UnhandledExceptionAppDomain.CurrentDomain.FirstChanceException可以允许我检查异常,但不能捕获并忽略。我没有看到一种将这些异常标记为使用这两种方法处理的方法。如果我错了,请纠正我。
贝茨韦斯特摩兰2014年

2

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

不过,有趣的是,我不想捕获这些异常,因为我总是记录所有可能导致应用程序崩溃的未处理异常(但是,这2个与我无关,显然不会或至少不应崩溃)它,但我可能错了。我怀疑这些错误是由于某些超时到期或从客户端显式取消而出现的,但我希望它们会在ASP.NET框架内被处理,而不会在未处理的异常之外传播出去。


就我而言,发生这些异常是因为浏览器在用户导航到新URL时取消了请求。
贝茨韦斯特摩兰2014年

1
我懂了。就我而言,请求是通过WinHTTPAPI 发出的,而不是通过浏览器发出的。
加布里埃尔S.14年

2

我发现了有关此错误的更多详细信息。可能发生2种可能的异常:

  1. OperationCanceledException
  2. TaskCanceledException

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

因此,提供的解决方法有助于部分缓解第一个异常,而对第二个异常则无济于事。在后一种情况下,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内容。虽然看起来不是很健壮。

PS这是我过滤掉这些错误的方法:

private static bool IsAspNetBugException(Exception exception)
{
    return
        (exception is TaskCanceledException || exception is OperationCanceledException) 
        &&
        exception.StackTrace.Contains("System.Web.HttpApplication.ExecuteStep");
}

1
在建议的代码中,您response在try内创建变量,然后在try之外将其返回。那不可能的工作可以吗?另外,您在哪里使用IsAspNetBugException?
Schoof

1
不,那是行不通的,只是必须在try / catch块之外声明它,并使用完成的任务进行初始化。但这仅仅是解决方案的一个例子,无论如何也不是防弹的。至于另一个问题,您可以在Global.Asax OnError处理程序中使用它。如果您不使用该消息记录消息,则无需担心。如果这样做,这是如何从系统中过滤掉“非错误”的一个示例。
Ilya Chernomordik '18

1

我们一直在收到相同的异常,我们尝试使用@dmatson的变通方法,但仍然会遇到一些异常。我们一直处理到最近。我们注意到一些Windows日志以惊人的速度增长。

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

大多数错误都与“ Timer_ConnectionIdle”有关。我四处搜索,似乎即使Web api调用已完成,连接仍然比原始连接持续了两分钟。

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

我添加response.Headers.ConnectionClose = true;了SendAsync MessageHandler,从中可以得知客户端正在关闭连接,而我们再也没有遇到这个问题。

我知道这不是最好的解决方案,但在我们的情况下有效。我也很确定性能,如果您的API接连收到来自同一客户端的多个调用,那么这不是您要执行的操作。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.