ChatGPT解决这个技术问题 Extra ChatGPT

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

当用户加载页面时,它会发出一个或多个 ajax 请求,这些请求会命中 ASP.NET Web API 2 控制器。如果用户导航到另一个页面,在这些 ajax 请求完成之前,这些请求会被浏览器取消。然后,我们的 ELMAH HttpModule 会为每个取消的请求记录两个错误:

错误一:

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 端点是异步和非异步方法的混合。

无论我在哪里添加错误日志记录,我都无法在用户代码 Global.asax Applicaiton_Error TaskScheduler.UnobservedTaskException ELMAH 错误过滤 void ErrorLog_Filtering 中捕获异常(https://code.google.com/p/elmah/wiki/ErrorFiltering)

Global.asax Applicaiton_Error

TaskScheduler.UnobservedTaskException

ELMAH 错误过滤 void ErrorLog_Filtering (https://code.google.com/p/elmah/wiki/ErrorFiltering)

我们在当前版本的 Katana 库中也看到了相同的异常(TaskCanceledException 和 OperationCanceledException)。
我发现了更多关于何时发生两种异常的详细信息,并发现此解决方法仅适用于其中一种。以下是一些详细信息:stackoverflow.com/questions/22157596/…

d
dmatson

这是 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;
    }
}

作为更新,这确实捕获了一些请求。我们仍然在我们的日志中看到不少。感谢您的解决方法。期待修复。
@KiranChalla - 我可以确认升级到 5.2.2 仍然有这些错误。
作为临时修复,为了阻止业务恐慌,我可以将以下代码添加到我的 ElmahExceptionFilter 中吗? public override void OnException(HttpActionExecutedContext context) { if (null != context.Exception && !(context.Exception is TaskCanceledException || context.Exception is OperationCanceledException)) { Elmah.ErrorSignal.FromCurrentContext().Raise(context.Exception); } base.OnException(context); }
当我尝试上述建议时,即使在将请求传递给 SendAsync 之前取消请求时,我仍然会收到异常(您可以通过在浏览器中按住 F5 的 url 来向您的 API 发出请求)来模拟这种情况。我还通过在对 SendAsync 的调用上方添加 if (cancellationToken.IsCancellationRequested) 检查解决了这个问题。现在,当浏览器快速取消请求时,异常不再显示。
我发现了更多关于何时发生两种异常的详细信息,并发现此解决方法仅适用于其中一种。以下是一些详细信息:stackoverflow.com/a/51514604/1671558
S
Shaddy Zeineddine

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

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

似乎这种方法的问题是错误仍然在 Global.asax 错误处理中弹出...事件虽然它没有发送到异常处理程序
h
huysentruitw

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

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

我主要从 OWIN 上下文中得到了这个错误,这个错误更好地针对它
I
Ilya Chernomordik

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

OperationCanceledException 任务取消异常

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

因此,提供的 workaround 有助于部分缓解第一个异常,但对第二个异常没有任何帮助。在后一种情况下,TaskCanceledException 发生在 base.SendAsync 调用自身期间,而不是将取消标记设置为 true。

我可以看到解决这些问题的两种方法:

只是忽略 global.asax 中的两个例外。那么问题来了,是否有可能突然忽略一些重要的事情呢?在处理程序中进行额外的尝试/捕获(尽管它不是防弹的 + 我们忽略的 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");
}

在您建议的代码中,您在 try 中创建 response 变量并在 try 之外返回它。那不可能工作吧?另外,您在哪里使用 IsAspNetBugException?
不,这是行不通的,它当然只需要在 try/catch 块之外声明,并使用完成任务之类的东西进行初始化。这只是解决方案的一个示例,无论如何都不是防弹的。至于其他问题,您在 Global.Asax OnError 处理程序中使用它。如果您不使用那个来记录您的消息,那么您无论如何都不必担心。如果这样做,这是一个如何从系统中过滤掉“非错误”的示例。
n
noseratio

您可以尝试将 default TPL task exception handling behavior 更改为 web.config

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

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

然而,看起来这个异常实际上是在 ASP.NET Web API 运行时的某个地方得到处理的,甚至在你有机会用你的代码处理它之前。

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


这些都不允许我处理异常。
@BatesWestmoreland,甚至没有 FirstChanceException?您是否尝试过使用跨 HTTP 请求持续存在的静态类来处理它?
我试图解决的问题是捕获并忽略这些异常。使用 AppDomain.UnhandledExceptionAppDomain.CurrentDomain.FirstChanceException 可以让我检查异常,但不能捕获和忽略。我没有看到使用这些方法中的任何一种将这些异常标记为已处理的方法。如果我错了,请纠正我。
抛出未观察到的任务异常是个坏主意;它默认关闭是有原因的。每个带有 async void 签名的方法都会抛出一个,以及任何在没有观察其结果的情况下被垃圾收集的任务。此外,这甚至与未处理的 TaskCancelledExceptions 无关。如果抛出 TaskCancelledException,则已观察到 Task(已取消)的结果。
G
Gabriel S.

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

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


在我的例子中,这些异常的发生是因为浏览器在用户导航到一个新的 url 时取消了请求。
我懂了。在我的例子中,请求是通过 WinHTTP API 发出的,而不是从浏览器发出的。
J
Jasel

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

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

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

M
Michael Margala

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

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

大多数错误都是针对“Timer_ConnectionIdle”的。我四处搜索,似乎即使 web api 调用完成,连接仍然持续超过原始连接两分钟。

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

我将 response.Headers.ConnectionClose = true; 添加到 SendAsync MessageHandler 中,据我所知,客户端正在关闭连接,我们不再遇到此问题。

我知道这不是最好的解决方案,但它适用于我们的案例。我也很确定,如果您的 API 从同一个客户端背靠背地获得多个调用,那么从性能方面来说,这不是您想要做的事情。