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

Blazor Server中使用EF+CancellationToken多次快速调用报错排查

问题分析与解决方案

错误原因

快速重复点击时,前一次请求的取消信号触发后,数据库会话可能仍处于未完全释放的状态,新请求复用了同一数据库连接(或会话),导致SQL Server检测到同一会话存在并行操作,抛出会话繁忙的错误。同时,EF的取消操作在某些时机不会直接抛出OperationCanceledException,而是触发SQL层面的取消信号,进而引发SqlException。

修复方案

1. 捕获并处理取消相关异常

在EF查询代码中捕获OperationCanceledException和包含“Operation cancelled by user”的SqlException,避免错误暴露到上层:

private async Task btnClick()
{
    if (prevCts is not null) prevCts.Cancel();
    cts = new CancellationTokenSource();
    prevCts = cts;
    var token = cts.Token;

    try
    {
        using var cx = cxf.CreateDbContext();
        var res = await cx.Lunches.ToArrayAsync(token);
        // 处理查询结果
    }
    catch (OperationCanceledException)
    {
        // 前一次请求被取消,无需处理
    }
    catch (SqlException ex) when (ex.Message.Contains("Operation cancelled by user"))
    {
        // 捕获SQL层面的取消异常,忽略即可
    }
}

2. 添加防抖逻辑减少重复请求

在按钮点击时设置最小响应间隔,短时间内重复点击只响应最后一次,从源头降低冲突概率:

private CancellationTokenSource cts;
private DateTime _lastClickTime = DateTime.MinValue;
private readonly TimeSpan _debounceInterval = TimeSpan.FromMilliseconds(300);

private async Task btnClick()
{
    var now = DateTime.UtcNow;
    if (now - _lastClickTime < _debounceInterval)
    {
        cts?.Cancel();
        cts = new CancellationTokenSource();
        return;
    }

    _lastClickTime = now;
    if (cts is not null) cts.Cancel();
    cts = new CancellationTokenSource();
    var token = cts.Token;

    try
    {
        using var cx = cxf.CreateDbContext();
        var res = await cx.Lunches.ToArrayAsync(token);
        // 处理结果
    }
    catch (OperationCanceledException)
    {
    }
    catch (SqlException ex) when (ex.Message.Contains("Operation cancelled by user"))
    {
    }
}

3. 优化DbContext连接配置(可选)

虽然使用IDbContextFactory每次创建新的DbContext,仍可通过配置减少连接池复用带来的会话冲突:

// 在Program.cs中配置DbContextFactory
builder.Services.AddDbContextFactory<MyContext>(options =>
{
    options.UseSqlServer(builder.Configuration.GetConnectionString("MyDb"),
        sqlOpts => sqlOpts.EnableRetryOnFailure()
                          .CommandTimeout(30));
});

关键说明

  • 取消操作不会立即终止数据库请求,只是发送信号,数据库需要时间响应,因此快速触发的新请求可能和未完全终止的旧请求产生会话冲突。
  • 防抖逻辑可以有效减少不必要的重复请求,降低冲突概率。
  • 捕获特定异常是为了避免取消操作引发的错误被误判为业务异常。

内容的提问来源于stack exchange,提问作者buga

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 03:15:38