Blazor Server页面加载时使用ContinueWith的技术疑问
Blazor Server 异步加载组件状态的疑问解答
你开发Blazor Server应用时,希望页面加载时同时调用API加载Item1和Item2,各自完成后立即显示,给出的实现代码如下:
protected override async Task OnInitializedAsync() { var t1 = _service.GetItem1().ContinueWith(t => { Item1Loaded = true; Item1State = t.Result; // 注:原代码此处疑似笔误,应为Item1State await InvokeAsync(StateHasChanged); }); var t2 = _service.GetItem2().ContinueWith(t => { Item2Loaded = true; Item2State = t.Result; await InvokeAsync(StateHasChanged); }); }
针对你的三个疑问,解答如下:
1. 用户离开页面后的任务取消与组件销毁问题
需要手动取消这些任务,Blazor框架不会自动处理未完成的异步任务。
- 当用户离开页面,组件会触发
Dispose(若实现IDisposable接口),此时未完成的任务后续修改状态变量、调用StateHasChanged可能引发异常——因为组件实例已被销毁,相关上下文不再有效。 - 解决方法:在组件中声明
CancellationTokenSource,将令牌传递给API调用,在组件销毁时取消令牌。示例:
private CancellationTokenSource _cts; protected override async Task OnInitializedAsync() { _cts = new CancellationTokenSource(); var token = _cts.Token; var t1 = _service.GetItem1(token).ContinueWith(t => { if (!token.IsCancellationRequested) { Item1Loaded = true; Item1State = t.Result; await InvokeAsync(StateHasChanged); } }, token); var t2 = _service.GetItem2(token).ContinueWith(t => { if (!token.IsCancellationRequested) { Item2Loaded = true; Item2State = t.Result; await InvokeAsync(StateHasChanged); } }, token); } public void Dispose() { _cts?.Cancel(); _cts?.Dispose(); }
注意需让组件实现IDisposable接口。
2. 同步上下文与InvokeAsync的作用
是的,必须确保回到Blazor的同步上下文执行状态修改和StateHasChanged调用。
- Blazor Server的组件运行在关联用户SignalR连接的特定同步上下文,跨线程修改组件状态会导致线程安全问题。
InvokeAsync会将传入的委托调度回组件的同步上下文执行,你代码中使用它的方式是正确的,能避免跨线程访问组件状态的异常。
3. ContinueWith与async/await的选择
这里用ContinueWith能实现需求,但用async/await的写法更易读、更符合现代C#异步编程规范,且同样能做到“任务完成即更新状态”。
- 你认为async/await无法按任务完成顺序执行延续操作是误解——async/await可以通过并行启动多个异步任务,各自独立await并处理结果。比如可以用以下代码替代:
private CancellationTokenSource _cts; protected override async Task OnInitializedAsync() { _cts = new CancellationTokenSource(); var token = _cts.Token; // 并行启动两个异步任务 var task1 = LoadItem1(token); var task2 = LoadItem2(token); // 可选:若不需要等待全部完成,可省略此句 await Task.WhenAll(task1, task2); } private async Task LoadItem1(CancellationToken token) { var result = await _service.GetItem1(token); if (!token.IsCancellationRequested) { Item1Loaded = true; Item1State = result; await InvokeAsync(StateHasChanged); } } private async Task LoadItem2(CancellationToken token) { var result = await _service.GetItem2(token); if (!token.IsCancellationRequested) { Item2Loaded = true; Item2State = result; await InvokeAsync(StateHasChanged); } }
这种写法逻辑清晰,更易维护,也避免了ContinueWith需要手动处理任务状态、调度上下文等细节问题。
内容的提问来源于stack exchange,提问作者Ben Zuill-Smith
相关产品推荐
相关产品推荐

