当用户加载页面时,它会发出一个或多个 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)
这是 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;
}
}
在为 WebApi 实现异常记录器时,建议扩展 System.Web.Http.ExceptionHandling.ExceptionLogger
类而不是创建 ExceptionFilter。 WebApi 内部不会为取消的请求调用 ExceptionLoggers 的 Log 方法(但是,异常过滤器会获取它们)。这是设计使然。
HttpConfiguration.Services.Add(typeof(IExceptionLogger), myWebApiExceptionLogger);
这是此问题的另一种解决方法。只需在捕获 OperationCanceledException
的 OWIN 管道的开头添加一个自定义 OWIN 中间件:
#if !DEBUG
app.Use(async (ctx, next) =>
{
try
{
await next();
}
catch (OperationCanceledException)
{
}
});
#endif
我发现了有关此错误的更多详细信息。有两种可能的例外情况:
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");
}
response
变量并在 try 之外返回它。那不可能工作吧?另外,您在哪里使用 IsAspNetBugException?
您可以尝试将 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.FirstChanceException
、here is how 将其作为第一次机会异常捕获。我知道这可能不是您想要的。
FirstChanceException
?您是否尝试过使用跨 HTTP 请求持续存在的静态类来处理它?
AppDomain.UnhandledException
或 AppDomain.CurrentDomain.FirstChanceException
可以让我检查异常,但不能捕获和忽略。我没有看到使用这些方法中的任何一种将这些异常标记为已处理的方法。如果我错了,请纠正我。
async void
签名的方法都会抛出一个,以及任何在没有观察其结果的情况下被垃圾收集的任务。此外,这甚至与未处理的 TaskCancelledExceptions 无关。如果抛出 TaskCancelledException,则已观察到 Task(已取消)的结果。
我有时会在我的 Web API 2 应用程序中遇到相同的 2 个异常,但是我可以使用 Global.asax.cs
中的 Application_Error
方法并使用通用 exception filter 来捕获它们。
有趣的是,虽然,我不想捕获这些异常,因为我总是记录所有可能导致应用程序崩溃的未处理异常(然而,这两个与我无关,显然不会或至少不应该崩溃它,但我可能是错的)。我怀疑这些错误的出现是由于某些超时到期或来自客户端的显式取消,但我希望它们在 ASP.NET 框架内部被处理,而不是作为未处理的异常传播到外部。
WinHTTP
API 发出的,而不是从浏览器发出的。
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>
我们一直收到相同的异常,我们尝试使用@dmatson 的解决方法,但我们仍然会遇到一些异常。直到最近我们才处理它。我们注意到一些 Windows 日志以惊人的速度增长。
错误文件位于:C:\Windows\System32\LogFiles\HTTPERR
大多数错误都是针对“Timer_ConnectionIdle”的。我四处搜索,似乎即使 web api 调用完成,连接仍然持续超过原始连接两分钟。
然后我想我们应该尝试关闭响应中的连接,看看会发生什么。
我将 response.Headers.ConnectionClose = true;
添加到 SendAsync MessageHandler 中,据我所知,客户端正在关闭连接,我们不再遇到此问题。
我知道这不是最好的解决方案,但它适用于我们的案例。我也很确定,如果您的 API 从同一个客户端背靠背地获得多个调用,那么从性能方面来说,这不是您想要做的事情。
不定期副业成功案例分享
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)
检查解决了这个问题。现在,当浏览器快速取消请求时,异常不再显示。