Blazor Server中使用PeriodicTimer刷新组件的最佳实践
定时while循环的最佳放置位置
你当前把循环写在OnInitializedAsync里是有隐患的:
- Blazor Server默认开启预渲染时,
OnInitializedAsync会在预渲染、正式交互两个阶段各执行一次,直接放这里会生成两个并行的定时器循环,产生重复的接口请求。 - 没有绑定组件生命周期的取消控制,组件销毁后循环不会自动停止,会造成内存泄漏,甚至触发组件释放后的UI更新报错。
正确的放置位置是OnAfterRenderAsync生命周期方法,仅在首次渲染完成(firstRender == true)后启动定时循环。这个时机点组件已经完成和客户端的SignalR连接绑定,也不会受预渲染重复执行逻辑的影响。
PeriodicTimer资源释放的额外处理
你现在仅在Dispose方法中调用_periodicTimer.Dispose()是不够的,还需要补两项处理:
- 给
WaitForNextTickAsync传入和组件生命周期绑定的CancellationToken,组件释放时触发令牌取消,让正在等待定时器的任务主动退出循环,避免任务滞留、触发对象已释放的异常。 - 捕获循环运行过程中的取消异常、业务请求异常,避免未处理异常导致组件崩溃、上报未捕获任务错误。
修正后的完整实现代码
public partial class Index : IDisposable { private readonly PeriodicTimer _periodicTimer = new(TimeSpan.FromMinutes(1)); private readonly CancellationTokenSource _disposeCts = new(); protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) return; await Service.Init(); // 丢弃返回值,不阻塞生命周期方法执行,后台运行定时循环 _ = ProcessPeriodicRefresh(_disposeCts.Token); } private async Task ProcessPeriodicRefresh(CancellationToken ct) { try { while (await _periodicTimer.WaitForNextTickAsync(ct)) { await Service.GetViewModels(); await InvokeAsync(StateHasChanged); } } catch (OperationCanceledException) { // 组件释放触发的正常取消,无需处理 } catch (Exception) { // 可按需添加日志记录、错误提示等逻辑 } } public void Dispose() { // 先触发取消,终止正在运行的定时任务 _disposeCts.Cancel(); _disposeCts.Dispose(); _periodicTimer.Dispose(); GC.SuppressFinalize(this); } }
额外注意点
- 启动定时任务时不要直接
await长生命周期的循环任务,否则会阻塞组件生命周期流程,导致页面渲染卡住。 - 如果你的应用全局关闭了预渲染,理论上也可以在
OnInitializedAsync启动循环,但依然建议统一放在OnAfterRenderAsync处理,兼容性更好。 - 不需要额外手动处理定时器的等待回调,PeriodicTimer本身的WaitForNextTickAsync在收到取消信号、或者定时器被Dispose时都会自动退出等待状态。
内容的提问来源于stack exchange,提问作者Lucian Bumb
相关产品推荐
相关产品推荐

