Blazor server端客户端关闭或离开页面时持续抛出System.ArgumentNullException异常
异常原因
该ArgumentNullException确实由客户端断开连接后定时器仍在运行导致:Blazor Server的页面组件与客户端通过SignalR Circuit绑定,当用户关闭窗口或导航离开时,Circuit会被销毁释放,但未手动停止的定时器仍会持续执行,调用StateHasChanged()或JS交互逻辑时依赖的上下文对象为空,就会抛出该异常。
修复方案
你只需要在页面组件中实现IAsyncDisposable接口,在组件销毁时自动释放定时器即可,Blazor会在用户离开页面时自动调用该接口的方法,无需额外编写客户端导航检测逻辑。
完整示例代码如下:
@implements IAsyncDisposable <!-- 你的页面渲染代码 --> @code { private Timer _autoUpdateTimer; private readonly CancellationTokenSource _destroyCts = new(); protected override void OnInitialized() { // 初始化每秒执行的定时器 _autoUpdateTimer = new Timer(TimerCallback, null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); } private async void TimerCallback(object state) { // 组件已销毁则直接终止逻辑 if (_destroyCts.IsCancellationRequested) return; try { // 你的业务更新逻辑 // 注意:UI更新必须包裹在InvokeAsync中执行 await InvokeAsync(StateHasChanged); } catch (ArgumentNullException) { // 捕获到断开导致的空异常时直接销毁资源 await DisposeAsync(); } catch (OperationCanceledException) { // 取消操作直接忽略 } } public async ValueTask DisposeAsync() { // 触发取消令牌,终止所有运行中的逻辑 await _destroyCts.CancelAsync(); // 释放定时器 if (_autoUpdateTimer != null) { await _autoUpdateTimer.DisposeAsync(); _autoUpdateTimer = null; } _destroyCts.Dispose(); } }
可选优化
如果你使用的是.NET 6及以上版本,可使用PeriodicTimer替代传统Timer,资源管理更简洁:
private PeriodicTimer _periodicTimer; protected override async Task OnInitializedAsync() { _periodicTimer = new PeriodicTimer(TimeSpan.FromSeconds(1)); // 启动后台定时任务 _ = RunAutoUpdateTask(); } private async Task RunAutoUpdateTask() { try { // 等待定时器触发,组件销毁时令牌会自动取消循环 while (await _periodicTimer.WaitForNextTickAsync(_destroyCts.Token)) { // 业务逻辑 + UI更新 await InvokeAsync(StateHasChanged); } } catch (OperationCanceledException) { // 忽略销毁触发的取消异常 } }
注意事项
- 所有跨线程的UI更新操作必须包裹在
InvokeAsync中执行,避免线程安全问题。 - 无需额外实现客户端断开监听逻辑,Blazor的Circuit生命周期会自动触发组件的
DisposeAsync方法。
内容的提问来源于stack exchange,提问作者sleepymatto
相关产品推荐
相关产品推荐

