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

.NET6 Blazor Server UnitOfWork释放报连接Connecting状态异常咨询

UnitOfWork 连接状态异常问题解答

异常基本信息

  • 报错内容:UnitOfWork Exception: Can't close, connection is in state Connecting
  • 触发条件:Blazor Server 页面加载耗时较长的数据库数据时,数据未加载完成就跳转离开页面,框架触发UnitOfWork实例释放时抛出异常,仅在异步查询未完成就执行Dispose的场景下复现。

涉及技术栈

  • .NET 6
  • Blazor Server Side V6.0.10
  • EF Core PostgreSQL V6.0.4
  • 数据访问层采用UnitOfWork(工作单元)模式封装

原始问题代码

public class UnitOfWork : IUnitOfWork, IDisposable
{
    private readonly FMDataContext _context;
    
    // 仓储实例定义省略

    private bool _disposed;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
            if (disposing)
                _context.Dispose();
        _disposed = true;
    }
}

问题1:异常修复方案 & Connecting状态DbContext的正确释放方式

根因

Blazor Server 页面切换时会立即释放当前页面对应作用域的注入服务,此时如果DbContext正在执行异步查询,底层Npgsql连接处于Connecting状态,同步调用Dispose()会尝试强制关闭未完成建立的连接,直接触发该异常。

正确修复步骤

  1. 优先实现异步释放逻辑
    EF Core 6+ 的DbContext本身实现了IAsyncDisposable接口,UnitOfWork需要同时实现IAsyncDisposable,异步释放流程会正确等待/取消正在进行的连接操作,不会出现同步Dispose强制关连接的问题。Blazor Server的服务容器会优先调用IAsyncDisposable的异步释放方法,从根源规避状态冲突。
  2. 主动取消未完成的数据库查询
    所有页面内的数据库异步查询必须传入取消令牌:在Blazor组件中重写生命周期方法时,使用框架提供的CancellationToken,或者自定义CancellationTokenSource,在组件释放时主动触发取消,等待正在执行的查询抛出OperationCanceledException终止后,再执行资源释放,从源头避免Dispose时还有正在进行的连接操作。

注意:绝对不能跳过处于Connecting状态的DbContext释放,否则会造成数据库连接泄漏,高并发下会快速耗尽连接池,导致整体数据访问故障。


问题2:拟议修复代码的合理性判定

你提供的「判断连接非Connecting状态才释放DbContext」的实现存在严重缺陷,完全不可用于生产环境,问题如下:

  • 资源泄漏:当连接处于Connecting状态时,代码直接跳过_context.Dispose()调用,后续没有任何补释放逻辑,DbContext持有的数据库连接会一直被占用,直到GC非确定性回收,短时间内多次触发该场景就会耗尽PostgreSQL连接池,导致全站数据库访问瘫痪。
  • 逻辑不可靠:连接状态存在竞态条件,判断状态的瞬间和实际执行Dispose的瞬间连接状态可能发生变化,该判断无法100%规避异常。

参考修复实现

public class UnitOfWork : IUnitOfWork, IDisposable, IAsyncDisposable
{
    private readonly FMDataContext _context;
    // 仓储实例定义省略
    private bool _disposed;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                // 同步释放仅作为兜底,优先走异步释放
                _context.Dispose();
            }
        }
        _disposed = true;
    }

    public async ValueTask DisposeAsync()
    {
        if (!_disposed)
        {
            // 异步释放会正确处理正在连接/执行的数据库操作
            await _context.DisposeAsync();
            _disposed = true;
        }
        GC.SuppressFinalize(this);
    }
}

额外优化:在Blazor组件的查询逻辑中传入取消令牌,页面销毁时主动取消未完成查询,示例:

@implements IAsyncDisposable
private CancellationTokenSource _cts = new();

protected override async Task OnInitializedAsync()
{
    // 查询时传入取消令牌
    var data = await _unitOfWork.SomeRepo.GetAllAsync(_cts.Token);
}

public async ValueTask DisposeAsync()
{
    // 页面释放时先取消所有未完成查询
    _cts.Cancel();
    _cts.Dispose();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 17:06:29