Blazor Server事件处理默认非线程安全,需用InvokeAsync确保UI更新线程安全,并配合CancellationToken及时取消异步操作以防ObjectDisposedException。
Blazor Server 使用 SignalR 长连接通信,所有 UI 事件(如 @onclick)都在同一个逻辑“请求上下文”中同步执行,但不等于线程安全。多个用户操作或快速连续点击可能触发并发调用,而组件实例是按用户会话单例复用的,StateHasChanged() 和字段更新若未加防护,会导致状态错乱或 ObjectDisposedException。
System.ObjectDisposedException: Cannot access a disposed object,尤其在异步操作完成前组件已卸载async Task 方法;后台任务(如 Timer)更新组件字段后调用 StateHasChanged()
StateHasChanged() —— 必须通过 InvokeAsync()
InvokeAsync() 包裹所有跨上下文的状态更新即使你在 async 方法中 await 了外部服务,回调一旦脱离原始渲染上下文(例如从 Task.Run、Timer 或第三方库回调中返回),就必须显式切回组件上下文才能安全更新状态或触发重绘。
private async Task HandleClick()
{
// ❌ 危险:若 DoWorkAsync 内部用了 Task.Run 或延迟回调,后续更新可能并发
var result = await DoWorkAsync();
currentData = result;
StateHasChanged(); // 可能抛 ObjectDisposedException
// ✅ 正确:确保所有 UI 更新都经 InvokeAsync 调度
await InvokeAsync(() =>
{
currentData = result;
StateHasChanged();
});
}
InvokeAsync(Action) 是线程安全的入口,Blazor 会排队执行并自动忽略已销毁组件的调用InvokeAsync;合并到一个委托里减少调度开销InvokeAsync 不足以防重复提交 —— 还需业务层节流或禁用按钮Task.Run 或长时间运行同步代码Blazor Server 渲染线程(即 SynchronizationContext)是单线程的。在事件处理器里直接调用阻塞式 IO 或 CPU 密集型代码(如 Thread.Sleep、大数组排序、XM
L 解析),会卡住整个用户的会话通道,影响响应甚至引发超时断连。
Task.Run(() => { Thread.Sleep(2000); return GetData(); }) —— 没解决根本问题,反而增加线程调度负担IHostedService),用 Channel 或 EventCallback 通知组件;或使用真正异步 API(HttpClient.GetAsync、Stream.ReadAsync)if (CancellationToken.IsCancellationRequested) return;,并在组件 Dispose 时传递取消令牌CancellationToken 主动取消挂起的异步操作用户导航离开页面、组件被销毁后,未完成的 await 任务仍可能继续执行,并在完成后尝试调用 InvokeAsync —— 此时组件已释放,InvokeAsync 会静默丢弃该调用,但后台任务仍在浪费资源。
private CancellationTokenSource? _cts;
protected override void OnInitialized()
{
_cts = new();
}
private async Task LoadData()
{
try
{
var data = await _api.GetDataAsync(_cts.Token); // 传入 token
await InvokeAsync(() =>
{
items = data;
StateHasChanged();
});
}
catch (OperationCanceledException)
{
// 被主动取消,无需处理
}
}
public void Dispose()
{
_cts?.Cancel();
_cts?.Dispose();
}
CancellationTokenSource,防止竞态残留IsDisposed 判断组件状态 —— InvokeAsync 已内置防护,重点是及时取消上游 I/OCancellationToken,考虑用 Task.WaitAsync(TimeSpan, CancellationToken) 包装超时InvokeAsync 的 UI 更新、任何未绑定取消令牌的 await、任何未清理的后台计时器,都会在高交互场景下暴露问题。