Blazor页面如何检测页面是否被离开,终止长时间运行任务
解决Blazor长任务在页面关闭/刷新时停止的问题
1. 基础方案:覆盖组件卸载与内部导航场景
你已经用到的NavigationManager.RegisterLocationChangingHandler可以处理Blazor应用内的导航跳转,结合IAsyncDisposable接口,能覆盖组件正常卸载的情况:
- 让组件实现
IAsyncDisposable,在DisposeAsync方法中取消长任务的CancellationTokenSource。 - 注册
LocationChangingHandler时,在回调里触发任务取消逻辑。
示例代码:
@implements IAsyncDisposable @inject NavigationManager NavManager @code { private CancellationTokenSource _cts = new(); private Task _longRunningTask; protected override void OnInitialized() { // 注册导航变化监听 NavManager.RegisterLocationChangingHandler(OnLocationChanging); base.OnInitialized(); } private async ValueTask OnLocationChanging(LocationChangingContext context) { // 取消长任务 _cts.Cancel(); await _longRunningTask; // 等待任务终止 } // 启动长任务的方法 public async Task StartLongTask() { _cts = new CancellationTokenSource(); _longRunningTask = ExecuteLongTask(_cts.Token); await _longRunningTask; } private async Task ExecuteLongTask(CancellationToken token) { try { // 你的下载、合并逻辑,全程传入token并检查取消状态 while (!token.IsCancellationRequested) { await DownloadFileFromVendorApi(token); await Task.Delay(1000, token); } } catch (OperationCanceledException) { // 任务被取消,执行资源清理 CleanupTempFiles(); } } public async ValueTask DisposeAsync() { _cts.Cancel(); if (_longRunningTask != null) { await _longRunningTask; } _cts.Dispose(); } }
2. 处理标签页关闭/刷新的场景
方案A:心跳检测(适配Blazor Server)
如果长任务运行在服务器端,可通过前端定时发送心跳,服务器端检测超时心跳来取消任务:
- 前端组件:用
Timer定时调用后端接口更新心跳时间戳。 - 后端服务:维护任务ID与最后心跳时间的映射,定时扫描超时(比如15秒未收到)的任务并取消。
示例代码(组件端):
private Timer _heartbeatTimer; protected override void OnAfterRender(bool firstRender) { if (firstRender) { // 每5秒发送一次心跳 _heartbeatTimer = new Timer(async _ => await SendHeartbeat(), null, 0, 5000); } base.OnAfterRender(firstRender); } private async Task SendHeartbeat() { if (_longRunningTask != null && !_cts.IsCancellationRequested) { await Http.PostAsync($"api/tasks/{CurrentTaskId}/heartbeat", null); } } // 在DisposeAsync中销毁定时器 public async ValueTask DisposeAsync() { _heartbeatTimer?.Dispose(); // ... 其他清理逻辑 }
方案B:JavaScript事件监听(适配Blazor WASM/Server)
通过JSInterop监听浏览器的pagehide和visibilitychange事件,这些事件在标签页关闭、刷新、切换时会触发,比beforeunload更稳定,且只需少量JS代码:
- 在wwwroot/js目录创建
pageEvents.js:
export function registerPageUnloadListener(callback) { // 监听页面关闭/刷新事件 document.addEventListener('pagehide', () => { callback(); }); // 监听标签切换导致的页面隐藏 document.addEventListener('visibilitychange', () => { if (document.hidden) { callback(); } }); }
- 在组件中引入并调用该JS模块:
@implements IAsyncDisposable @inject IJSRuntime JSRuntime @code { private IJSObjectReference _jsModule; private CancellationTokenSource _cts = new(); protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./js/pageEvents.js"); // 注册监听,传入任务取消回调 await _jsModule.InvokeVoidAsync("registerPageUnloadListener", () => _cts.Cancel()); } await base.OnAfterRenderAsync(firstRender); } // ... 长任务逻辑同基础方案 public async ValueTask DisposeAsync() { _cts.Cancel(); if (_jsModule != null) { await _jsModule.DisposeAsync(); } _cts.Dispose(); } }
关键注意事项
- 所有长任务的异步操作必须传入
CancellationToken,并在关键步骤检查token.IsCancellationRequested,确保任务能及时终止。 - Blazor Server场景下,还可实现
ICircuitHandler接口,在CircuitClosed方法中直接取消当前用户的所有长任务。
内容的提问来源于stack exchange,提问作者SSteve
相关产品推荐
相关产品推荐

