.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()会尝试强制关闭未完成建立的连接,直接触发该异常。
正确修复步骤
- 优先实现异步释放逻辑
EF Core 6+ 的DbContext本身实现了IAsyncDisposable接口,UnitOfWork需要同时实现IAsyncDisposable,异步释放流程会正确等待/取消正在进行的连接操作,不会出现同步Dispose强制关连接的问题。Blazor Server的服务容器会优先调用IAsyncDisposable的异步释放方法,从根源规避状态冲突。 - 主动取消未完成的数据库查询
所有页面内的数据库异步查询必须传入取消令牌:在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
相关产品推荐
相关产品推荐

