Blazor Server中Timer释放后访问引发已释放对象异常的问题求助
问题
我在Blazor Server应用的主组件MainLayout.razor中使用System.Threading.Timer,每隔4分钟触发一个端点上报用户的活跃状态。代码如下:
@implements IDisposable . . . private System.Threading.Timer Timer ; protected override void OnInitialized() { Timer = new System.Threading.Timer(_ => { if (applicationUser.LogedIn) { InvokeAsync(updateLastSeen); } }, null, 240000, 240000); } void IDisposable.Dispose() { Timer ?.Dispose(); Timer = null; }
多数情况下代码运行正常,但偶尔会抛出以下异常导致应用重启:
warn: Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer[100] Unhandled exception rendering component: Cannot access a disposed object. Object name: 'Timer'. System.ObjectDisposedException: Cannot access a disposed object. Object name: 'Timer'. at System.Timers.Timer.set_Enabled(Boolean value) at Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.<>c__DisplayClass32_0`1.<CreateBinderCore>b__0(ChangeEventArgs e) --- End of stack trace from previous location --- at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState) fail: Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost[111] Unhandled exception in circuit 'wO6hOy44Wd0kK3zpgb5w4E-9Frulb4mArReLXZ89NkI'. System.ObjectDisposedException: Cannot access a disposed object. Object name: 'Timer'. at System.Timers.Timer.set_Enabled(Boolean value) at Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.<>c__DisplayClass32_0`1.<CreateBinderCore>b__0(ChangeEventArgs e) --- End of stack trace from previous location --- at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
我已经遵循了相关文档的实现规范,想知道这个问题的根源是什么?
问题根源与解决方案
根源分析
这个异常的核心是Timer回调与组件Dispose操作的线程竞争:
- 当Blazor Server的电路(Circuit)销毁(比如用户关闭浏览器、超时断开),组件的
Dispose方法会被调用释放Timer实例,但此时Timer的回调可能已在另一个线程启动,或者InvokeAsync(updateLastSeen)的异步操作还在执行,后续逻辑尝试访问已释放的Timer对象就会抛出ObjectDisposedException。 - 异常栈里的
System.Timers.Timer.set_Enabled提示:你的updateLastSeen方法里大概率存在修改Timer Enabled属性的逻辑,当Timer已被Dispose后,修改这个属性直接触发了错误。
修复方案
- 添加线程安全的生命周期跟踪
引入CancellationTokenSource跟踪组件状态,在Dispose时取消令牌,同时用Interlocked保证Timer实例的线程安全访问:
@implements IDisposable . . . private System.Threading.Timer _timer; private readonly CancellationTokenSource _cts = new(); protected override void OnInitialized() { _timer = new System.Threading.Timer(async _ => { // 组件已销毁则直接退出 if (_cts.IsCancellationRequested) return; if (applicationUser.LogedIn) { await InvokeAsync(updateLastSeen); } }, null, 240000, 240000); } void IDisposable.Dispose() { // 先取消令牌,阻止后续回调执行 _cts.Cancel(); _cts.Dispose(); // 原子操作替换Timer实例,避免Dispose时的线程竞争 var timer = Interlocked.Exchange(ref _timer, null); timer?.Dispose(); }
检查并修正
updateLastSeen方法
如果该方法中有操作Timer的逻辑(如修改Enabled、重置间隔),必须先判断Timer是否已释放,或者结合CancellationToken在组件销毁后跳过相关操作。优化Timer初始化时机
可以考虑在OnAfterRenderAsync中初始化Timer,确保组件完成渲染后再启动定时任务,减少生命周期时序冲突。
内容的提问来源于stack exchange,提问作者Marko
相关产品推荐
相关产品推荐

