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

多线程场景下如何实现前序任务的线程安全取消?

线程安全的任务取消方案(针对多线程触发的connect事件)

问题背景

在connect事件的处理程序中,需要启动一个耗时较长的异步任务并等待结果;当该任务运行时,connect事件可能从不同线程再次触发,此时需要中断前序任务并启动新任务,且取消操作必须保证线程安全。

现有非线程安全实现

最初的实现未考虑多线程并发场景,存在线程安全隐患:

CancellationTokenSource cts;
        
async void OnConnected() 
{
    cts?.Cancel();
    var currentCts = cts = new CancellationTokenSource();   

    try
    {
        var result = await LongRunningTask(currentCts.Token);
        //Do something with result if LongRinningTask was not cancelled
    }
    finally
    {
        currentCts.Dispose();               
    }
}
    
async Task<bool> LongRunningTask(CancellationToken ct)
{
    try
    {
        await Task.Delay(TimeSpan.FromHours(1), ct);
        return true;
    }       
    catch(Exception)
    {
        return false;
    }
}

使用lock语句能否实现线程安全?

可以。用lock包裹CancellationTokenSource的取消与替换逻辑,能确保同一时刻只有一个线程执行这段代码,避免多线程并发修改cts引用导致的前序任务未取消、引用错乱等问题。优化后的代码如下:

CancellationTokenSource cts;
readonly object ctsSync = new object();
        
async void OnConnected() 
{
    CancellationTokenSource currentCts;
    lock(ctsSync)
    {
        cts?.Cancel();
        currentCts = cts = new CancellationTokenSource();   
    }

    try
    {
        var result = await LongRunningTask(currentCts.Token);
        if (!currentCts.IsCancellationRequested)
        {
            // 仅处理未被取消的任务结果
        }
    }
    finally
    {
        currentCts.Dispose();               
    }
}

需要注意:

  • 必须在lock块内完成currentCts的赋值,避免线程间引用不一致
  • LongRunningTask内部要正确响应取消信号,优先使用支持取消的异步API(如Task.Delay(..., ct)),或在关键步骤检查ct.IsCancellationRequested

更优解决方案:无锁原子操作

针对这种仅需替换引用并取消旧实例的场景,使用Interlocked类的原子操作替代lock,可以避免锁的上下文切换开销,性能更优。示例代码:

private CancellationTokenSource _cts;

async void OnConnected()
{
    // 原子交换:获取旧的cts实例,同时将新实例赋值给字段
    var oldCts = Interlocked.Exchange(ref _cts, new CancellationTokenSource());
    
    // 取消并释放旧任务的cts
    oldCts?.Cancel();
    oldCts?.Dispose();

    var currentCts = _cts;
    try
    {
        var result = await LongRunningTask(currentCts.Token);
        // 仅当当前任务未被取消时处理结果
        if (!currentCts.IsCancellationRequested)
        {
            // 处理任务结果逻辑
        }
    }
    finally
    {
        // 仅当当前cts仍是全局字段的引用时,才释放并清空字段
        // 避免重复释放已被替换的cts实例
        if (ReferenceEquals(_cts, currentCts))
        {
            currentCts.Dispose();
            Interlocked.CompareExchange(ref _cts, null, currentCts);
        }
    }
}

async Task<bool> LongRunningTask(CancellationToken ct)
{
    try
    {
        await Task.Delay(TimeSpan.FromHours(1), ct);
        return true;
    }
    catch (OperationCanceledException)
    {
        // 精准捕获取消操作异常,避免误处理其他错误
        return false;
    }
    catch (Exception)
    {
        // 处理其他业务异常
        return false;
    }
}

该方案的核心优势:

  • 用Interlocked.Exchange实现无锁线程安全,性能优于lock
  • 明确捕获OperationCanceledException,区分任务取消与其他异常
  • 新增引用检查,避免重复释放CancellationTokenSource实例
  • 在处理结果前校验取消状态,避免无效的结果处理逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 11:05:47