You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Server Side Blazor消息系统中CancellationTokenSource已释放问题排查

问题描述

我负责的项目基于Server Side Blazor搭建内置消息系统,使用SQL Server与Firebase:消息收发通过SQL Server实现,实时消息推送依赖Firebase。

当用户选择对话线程后,客户端会订阅Firebase中的消息及对话方状态。由于这些操作耗时较长,我采用先展示界面、后台执行操作的方案。但问题在于,用户可能在完成前一线程的所有订阅操作前就切换到其他线程。

为处理该场景,我尝试编写如下代码:

@code { 
    private bool _processing = true;           
    private CancellationTokenSource _cts = new CancellationTokenSource();   
    private CancellationToken? _token;     
    private Task? _slowOperations;          

    private async Task GetMessagesAsync()     
    {     
        _processing = true;     
        // SQL Server operations     
        _processing = false;              
        StateHasChanged(); 

        if (_slowOperations != null && !_slowOperations.IsCompletedSuccessfully)      
        {     
            _cts.Cancel();              
            _cts.Dispose();              
            _cts = new CancellationTokenSource();     
            _token = _cts.Token;     
        }              

        _slowOperations = Task.Run(() => ProceedSlowOperations(_token!.Value));              
    } 
         
    private async Task ProceedSlowOperations (CancellationToken cancellationToken)     
    {     
        await Task.Delay(5000);     

        if (!cancellationToken.IsCancellationRequested)     
        {     
            // make firebase subscriptions.     
        }              
 }

预期效果是用户多次切换线程时,仅在最后一次切换5秒后执行订阅,重置之前的调用。但实际使用中,切换线程的某些时段会出现The CancellationTokenSource has been disposed.错误,即使移除所有_cts.Dispose()调用,错误仍会发生。请问我哪里处理不当?

问题分析与修复方案

错误原因

  1. 令牌绑定已释放的Cts:传递给旧ProceedSlowOperations任务的_token,指向的是已被Dispose()的CancellationTokenSource。当旧任务后续检查cancellationToken.IsCancellationRequested时,会触发已释放资源的错误。
  2. 状态更新时序混乱:更新_token为新Cts的令牌后,旧任务仍持有旧令牌(对应已释放的Cts),导致资源访问冲突。
  3. 并发操作未同步:快速切换线程时,多个GetMessagesAsync调用可能同时操作_cts和_slowOperations,引发状态不一致。

修复后的代码

@code { 
    private bool _processing = true;           
    private CancellationTokenSource? _currentCts;     
    private Task? _slowOperations;          

    private async Task GetMessagesAsync()     
    {     
        _processing = true;     
        // SQL Server operations     
        _processing = false;              
        StateHasChanged(); 

        // 取消并清理旧任务
        if (_slowOperations != null && !_slowOperations.IsCompleted)      
        {     
            _currentCts?.Cancel();
            try
            {
                // 等待旧任务完成(包括取消),避免资源泄漏
                await _slowOperations;
            }
            catch (OperationCanceledException)
            {
                // 取消异常无需处理
            }
            finally
            {
                _currentCts?.Dispose();
                _currentCts = null;
                _slowOperations = null;
            }
        }              

        // 创建新的Cts和任务
        _currentCts = new CancellationTokenSource();
        var token = _currentCts.Token;
        _slowOperations = ProceedSlowOperations(token);              
    } 
         
    private async Task ProceedSlowOperations (CancellationToken cancellationToken)     
    {     
        await Task.Delay(5000, cancellationToken); // 将令牌传入Delay,取消时立即终止

        if (!cancellationToken.IsCancellationRequested)     
        {     
            // 执行Firebase订阅操作
        }              
    }
}

关键改进点

  • 为每个新任务创建独立的CancellationTokenSource,避免旧令牌与新Cts绑定混乱。
  • 取消旧任务后等待其完成再清理资源,确保旧任务不会访问已释放的Cts。
  • 将CancellationToken传递给Task.Delay,取消时Delay会立即终止,无需等待5秒。
  • 移除全局_token变量,直接使用当前Cts的令牌,避免引用过时资源。

内容的提问来源于stack exchange,提问作者Michel E

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 16:40:30