在Blazor的Dispose方法中调用业务逻辑是否存在弊端?
背景说明
举个例子,假设存在一个Blazor页面用于追踪用户的页面访问行为,会更新DateTime值以记录页面的最后查看时间。
我希望能够追踪用户离开页面的时间,将最后查看时间值更新为用户离开的时间,而非用户首次进入页面的时间。
就我目前了解的方案,我可以使用NavigationManager及其包含的事件监听URL变化,以此判断用户是否离开页面,但该方案过于复杂,我认为存在更简便的实现方式。
问题描述
若我的Blazor页面按如下方式实现IDisposable接口:
public partial class RazorPage : IDisposable { private bool disposedValue; // ... protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { TriggerNotificationUpdate(); } disposedValue = true; } } public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); GC.SuppressFinalize(this); } private void TriggerNotificationUpdate() { // ... } }
其中TriggerNotificationUpdate()会调用通过依赖注入注入的作用域服务,实现方式如下:
private void TriggerNotificationUpdate() { _ = Task.Run(async () => { await _service.UpdateTimestamp(); }); }
请问这种代码实现方式是否存在弊端,或是有哪些重要原因导致我不应该在代码中使用这类逻辑?
存在的核心弊端
- 作用域服务已销毁的风险
Blazor 组件的作用域和生命周期完全绑定,当Dispose方法被触发时,当前组件对应的DI作用域已经开始销毁流程,你注入的_service作为作用域服务,大概率已经被容器释放。此时调用UpdateTimestamp极大概率触发ObjectDisposedException,就算包裹在Task.Run中异步执行也无法避免,因为服务实例本身已经不可用。 - Fire-and-Forget模式的不可控风险
你使用_ = Task.Run(...)的丢弃写法,相当于完全放弃了对异步操作的观测:所有执行过程中抛出的异常都会被静默吞掉,你完全无法知道时间更新是否成功、有没有报错,排查问题毫无抓手。如果是Blazor Server场景,未捕获的后台异步异常甚至会直接导致服务进程崩溃,风险极高。 - Dispose执行时机不符合需求
- Blazor WebAssembly场景下,如果用户直接关闭标签页、退出浏览器,组件的
Dispose方法根本不会执行,你的时间更新逻辑完全触发不了; - Blazor Server场景下,如果用户断网、强制关闭页面,
Dispose需要等待电路超时(默认是300秒)才会触发,此时记录的时间和用户真实离开时间差了好几分钟,完全没有参考价值; - 用户刷新当前页面时,旧组件的
Dispose会触发,但用户实际并没有离开页面,会产生错误的离开时间记录。
- Blazor WebAssembly场景下,如果用户直接关闭标签页、退出浏览器,组件的
- 额外的性能损耗
Task.Run会将操作调度到线程池执行,对于这种轻量的接口调用完全没有必要,平白增加了线程调度开销。
更稳妥的实现方案
如果不想用NavigationManager监听路由变化,更推荐你用JS interop监听浏览器的beforeunload事件,触发时调用后端接口更新时间,覆盖页面关闭、刷新、跳转等所有用户离开场景,不会有Dispose时机不对的问题。
内容的提问来源于stack exchange,提问作者Aria Bounds
相关产品推荐
相关产品推荐

