You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET 7中如何避免记录取消令牌引发的数据库查询异常?

过滤ASP.NET 7.0中任务取消异常的日志记录

问题场景

我正在开发ASP.NET 7.0应用,会向数据库发起请求并传递CancellationToken,核心代码如下:

public async Task OnGetAsync(CancellationToken cancellationToken)
{
    var ls = await _db.MyTable
        .ToListAsync(cancellationToken: cancellationToken);
}

当取消令牌触发时,系统会生成以下截断后的异常日志:

fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[1]
   => SpanId:222e09bb9f77d813, TraceId:fe07899d8a16234fe2ea9c4d5f987e7f, ParentId:0000000000000000 => ConnectionId:0HMOV3KNV8H7R => RequestPath:/ RequestId:0HMOV3KNV8H7R:00000047
   An unhandled exception has occurred while executing the request.
   System.Threading.Tasks.TaskCanceledException: A task was canceled.
      at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
      at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
      at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
      at myApp.Areas.UsersAccess.Pages.SummaryModel.OnGetAsync(CancellationToken cancellationToken) in C:\Users\me\source\repos\me\myApp\myApp\Areas\UsersAccess\Pages\Summary\Summary.cshtml.cs:line 100
      at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.NonGenericTaskHandlerMethod.Execute(Object receiver, Object[] arguments)
      at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync()
      at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync()
      at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Rethrow(PageHandlerExecutedContext context)
      at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
      at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeInnerFilterAsync()
      at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
      at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
      at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
      at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
      at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
      at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
      at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
      at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
      at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
      at Program.<<<Main>$>b__0_8>d.MoveNext() in C:\Users\me\source\repos\me\myApp\myApp\Program.cs:line 117
   --- End of stack trace from previous location ---
      at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddlewareImpl.<Invoke>g__Awaited|8_0(ExceptionHandlerMiddlewareImpl middleware, HttpContext context, Task task)

这类日志会造成日志污染,我希望避免记录该异常。由于存在大量类似查询,为保证代码简洁,不想每次都使用try/catch块。

核心问题:是否有无需使用try/catch块即可过滤此特定日志消息的方法?

已尝试的方案

1. 修改日志配置

在appsettings.json的LogLevel部分添加以下配置,但未生效:

"System.Threading.Tasks.TaskCanceledException": "None"

推测原因是:需要将ExceptionHandlerMiddleware的日志级别设为None,但这样会过滤掉该中间件的所有日志,无法仅针对任务取消异常。

2. 自定义异常处理中间件

添加了自定义异常处理中间件,但错误仍会被记录:

app.UseExceptionHandler(errorApp =>
{
    errorApp.Run(async context =>
    {
        var exception = context.Features.Get<IExceptionHandlerFeature>()?.Error;

        if (exception?.Message == "A task was canceled.")
        {
            await context.Response.WriteAsync("Task cancelled.");
            // 不知道如何在此之后忽略该错误
        }
        else
        {
            // 重定向到错误页面
            context.Response.Redirect("/Error");
        }
    });
});

尝试修改context变量,但context.Features.Get<IExceptionHandlerFeature>().Error是只读的,无法直接设为null。

更新:查看.NET默认异常处理源码

查看.NET默认异常处理程序的HandleException函数,发现其中已处理操作取消的情况:

private async Task HandleException(HttpContext context, ExceptionDispatchInfo edi)
{
    if ((edi.SourceException is OperationCanceledException || edi.SourceException is IOException) && context.RequestAborted.IsCancellationRequested)
    {
        _logger.RequestAbortedException();

        if (!context.Response.HasStarted)
        {
            context.Response.StatusCode = StatusCodes.Status499ClientClosedRequest;
        }

        return;
    }

    _logger.UnhandledException(edi.SourceException);
    ...
}

但我的场景中代码并未进入该if分支,而是直接调用了UnhandledException方法。如果进入分支,会调用RequestAbortedException记录Debug级别的日志,这正是我需要的。不清楚为何代码未进入该分支,是操作有误、逻辑错误还是.NET代码问题?


解决方案

原因分析

默认中间件的判断条件是(异常是OperationCanceledException/IOException) 并且 context.RequestAborted.IsCancellationRequested。你遇到的TaskCanceledException虽然继承自OperationCanceledException,但如果传递的CancellationToken不是HttpContext.RequestAborted(而是自定义令牌),会导致context.RequestAborted.IsCancellationRequested为false,从而不满足进入分支的条件。

具体解决方法

方法1:确保传递的是HttpContext.RequestAborted

将自定义令牌替换为请求上下文自带的取消令牌,这样当请求被取消时,context.RequestAborted.IsCancellationRequested会自动为true,默认中间件会自动处理,仅记录Debug级日志:

// Razor Page中直接使用上下文的取消令牌
public async Task OnGetAsync()
{
    var ls = await _db.MyTable.ToListAsync(HttpContext.RequestAborted);
}

方法2:自定义日志过滤器

如果必须使用自定义令牌,可通过自定义日志过滤器过滤TaskCanceledException的日志:

  1. 实现日志过滤器:
public class TaskCanceledExceptionLoggerProvider : ILoggerProvider
{
    private readonly ILoggerProvider _innerProvider;

    public TaskCanceledExceptionLoggerProvider(ILoggerProvider innerProvider)
    {
        _innerProvider = innerProvider;
    }

    public ILogger CreateLogger(string categoryName)
    {
        var innerLogger = _innerProvider.CreateLogger(categoryName);
        return new TaskCanceledExceptionLogger(innerLogger);
    }

    public void Dispose() => _innerProvider.Dispose();

    private class TaskCanceledExceptionLogger : ILogger
    {
        private readonly ILogger _innerLogger;

        public TaskCanceledExceptionLogger(ILogger innerLogger)
        {
            _innerLogger = innerLogger;
        }

        public IDisposable BeginScope<TState>(TState state) => _innerLogger.BeginScope(state);

        public bool IsEnabled(LogLevel logLevel) => _innerLogger.IsEnabled(logLevel);

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            // 过滤ExceptionHandlerMiddleware中TaskCanceledException的Error级日志
            if (exception is TaskCanceledException 
                && eventId.Id == 1 
                && logLevel == LogLevel.Error
                && typeof(ExceptionHandlerMiddleware).FullName == state.ToString().Split(' ')[0])
            {
                return;
            }
            _innerLogger.Log(logLevel, eventId, state, exception, formatter);
        }
    }
}
  1. 在Program.cs中注册过滤器:
var loggerFactory = app.Services.GetRequiredService<ILoggerFactory>();
var consoleProvider = loggerFactory.GetProviders().First(p => p is ConsoleLoggerProvider);
loggerFactory.AddProvider(new TaskCanceledExceptionLoggerProvider(consoleProvider));

方法3:修改自定义异常中间件,标记异常已处理

在自定义中间件中移除IExceptionHandlerFeature,阻止后续日志记录:

app.UseExceptionHandler(errorApp =>
{
    errorApp.Run(async context =>
    {
        var exceptionFeature = context.Features.Get<IExceptionHandlerFeature>();
        var exception = exceptionFeature?.Error;

        if (exception is TaskCanceledException)
        {
            context.Response.StatusCode = StatusCodes.Status499ClientClosedRequest;
            await context.Response.WriteAsync("Task cancelled.");
            
            // 移除异常特性,阻止默认中间件记录日志
            context.Features.Set<IExceptionHandlerFeature>(null);
        }
        else
        {
            context.Response.Redirect("/Error");
        }
    });
});

注意:需确保该中间件在默认异常处理中间件之前注册,或直接替换默认异常处理逻辑。


内容的提问来源于stack exchange,提问作者S. Delsad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 03:08:15