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

调用异步方法新实例时,如何取消旧实例的执行?

异步方法"刷新"机制失效排查与修复

问题描述

需要实现异步方法Foo的"刷新"效果:每当调用该方法的新实例时,自动取消任何正在运行的旧实例。尝试使用静态CancellationTokenSource实现,但测试发现旧实例未按预期返回null。

原实现代码:

public static MyClass 
{
  static CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();

  public static async Task<Object> foo()
  {
    Object out = null; // 语法错误:out是C#关键字
    var previousTokenSource = Interlocked.Exchange(ref _cancellationTokenSource, new CancellationTokenSource());
    CancellationToken currentToken = _cancellationTokenSource.Token;
    try
    {
      previousTokenSource?.Cancel();

      await Task.Delay(1000, currentToken);
      out = ArbitraryLogic(currentToken);
    }
    catch (TaskCanceledException ex)
    {
      return null;
    }

    return out;
  }
}

测试代码:

public async void Test()
{
    var out1 = MyClass.foo();
    await Task.Delay(10);
    var out2 = MyClass.foo();

    await out1;
    await out2;
    if (out1.Result != null)
        Assert.Fail("out1.Result not null");
}

错误原因分析

  1. 取消信号未关联到旧任务:旧任务使用的是自己创建的新CancellationTokenSource的Token,而新任务取消的是更早的TokenSource,导致旧任务无法收到取消信号,逻辑完全错位。
  2. 异常捕获范围不足:仅捕获TaskCanceledException,但OperationCanceledException是其基类,部分取消场景(如手动调用token.ThrowIfCancellationRequested())会抛出基类异常,无法被捕获,导致旧任务不会返回null。
  3. 资源未释放:频繁创建CancellationTokenSource但未调用Dispose,会造成内存泄漏。
  4. 语法错误:使用关键字out作为变量名,代码无法正常编译。
  5. 测试代码不规范:async void会导致测试框架无法捕获异步异常,可能掩盖问题。

修正后的实现

public static class MyClass
{
    private static CancellationTokenSource _activeCts = new CancellationTokenSource();

    public static async Task<object> Foo()
    {
        // 原子替换旧的TokenSource,拿到旧实例用于取消
        var oldCts = Interlocked.Exchange(ref _activeCts, new CancellationTokenSource());
        var currentToken = _activeCts.Token;

        try
        {
            // 取消旧任务并释放资源
            oldCts.Cancel();
            oldCts.Dispose();

            // 异步操作监听当前Token
            await Task.Delay(1000, currentToken);
            // 业务逻辑中主动检查取消信号
            return ArbitraryLogic(currentToken);
        }
        catch (OperationCanceledException)
        {
            // 捕获所有取消相关异常,返回null
            return null;
        }
    }

    private static object ArbitraryLogic(CancellationToken token)
    {
        // 执行业务逻辑前先检查是否已取消
        token.ThrowIfCancellationRequested();
        
        // 若业务逻辑耗时较长,需定期检查取消信号
        // 示例:if (token.IsCancellationRequested) token.ThrowIfCancellationRequested();
        
        return new object();
    }
}

修正后的测试代码

public async Task Test() // 改用async Task确保测试框架能捕获异常
{
    var out1 = MyClass.Foo();
    await Task.Delay(10);
    var out2 = MyClass.Foo();

    var result1 = await out1;
    var result2 = await out2;
    
    Assert.IsNull(result1, "旧任务未被正确取消");
    Assert.IsNotNull(result2, "新任务未正常执行");
}

关键修正点说明

  • 原子交换逻辑:使用Interlocked.Exchange确保多线程下安全替换CancellationTokenSource,保证每次新任务都能准确取消上一个任务。
  • 全面异常处理:捕获OperationCanceledException覆盖所有取消场景,确保旧任务被取消后返回null。
  • 资源管理:取消旧CancellationTokenSource后立即释放资源,避免内存泄漏。
  • 业务逻辑取消检查:在自定义业务逻辑中主动检查取消信号,确保异步操作完成后仍能响应取消。
  • 测试规范:使用async Task替代async void,让测试框架能正确捕获异步异常,同时使用标准断言方法提升可读性。

内容的提问来源于stack exchange,提问作者Nicholas van Huyssteen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 13:45:32