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

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后,修改这个属性直接触发了错误。

修复方案

  1. 添加线程安全的生命周期跟踪
    引入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();
}
  1. 检查并修正updateLastSeen方法
    如果该方法中有操作Timer的逻辑(如修改Enabled、重置间隔),必须先判断Timer是否已释放,或者结合CancellationToken在组件销毁后跳过相关操作。

  2. 优化Timer初始化时机
    可以考虑在OnAfterRenderAsync中初始化Timer,确保组件完成渲染后再启动定时任务,减少生命周期时序冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 23:32:28