Blazor Server定时更新数据按F5刷新触发System.ObjectDisposedException求解
报错根本原因
该异常的核心诱因是Blazor组件生命周期与System.Threading.Timer异步回调的执行时机冲突,具体逻辑如下:
- Blazor Server模式下,用户按下F5刷新页面时,当前页面对应的组件会立即触发销毁流程,组件绑定的渲染器(Renderer)会被优先标记为已释放状态
System.Threading.Timer的回调委托签名为void TimerCallback(object state),你传入的异步lambda会被编译为async void类型,这类异步回调的执行不会被.NET运行时追踪等待- 你在
Dispose方法中调用_timer.Dispose()仅能停止定时器调度后续的新回调,已经启动执行的回调不会被终止。当这个正在运行的回调走完await TimerProcessGetValue()/await Task.Delay(500)逻辑后,再执行await InvokeAsync(StateHasChanged)时,渲染器已经被释放,就会抛出ObjectDisposedException。
三种测试方案的生效/失效原理
- ① 带
WaitHandle参数的Timer.Dispose方案失效:WaitHandle仅会等待所有已排队未启动的回调执行完成,已经进入执行状态的异步回调中的await逻辑不在Timer的管控范围内,所以等待500ms也无法拦截已经运行中的回调调用StateHasChanged。 - ② 改用
System.Timers.Timer方案生效:System.Timers.Timer的Elapsed事件默认会吞掉所有回调内的未捕获异常,即使InvokeAsync抛出了释放异常,也会被Timer内部消化,不会向上抛出报错。 - ③ 增加异常捕获方案生效:直接捕获了回调内抛出的释放异常,主动忽略后自然不会触发展示报错。
推荐的标准修复方案
相比上述三种方案,更规范的实现是增加组件销毁标记位,从逻辑上避免销毁后调用渲染接口,不会吞掉正常业务异常也没有多余开销:
@code { // 新增组件销毁标记 private bool _disposed; protected override void OnInitialized() { // 原有初始化逻辑保持不变 using (var dbContext = DbFactory.CreateDbContext()) { this.recipes = dbContext.Recipes.ToList(); this.planOrders = dbContext.PlanOrders.ToList(); this.nowOrders = dbContext.NowOrders.ToList(); } _timer = new System.Threading.Timer(async (_) => { Time = DateTime.Now.ToString(); databaseValue = await TimerProcessGetValue(); // 先判断组件状态再执行渲染 if (!_disposed) { await InvokeAsync(StateHasChanged); } }, null, 0, 1000); } public void Dispose() { // 先标记销毁再释放定时器 _disposed = true; _timer?.Dispose(); } // 原有其他逻辑保持不变 }
内容的提问来源于stack exchange,提问作者BTOM
相关产品推荐
相关产品推荐

