调用异步方法新实例时,如何取消旧实例的执行?
异步方法"刷新"机制失效排查与修复
问题描述
需要实现异步方法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"); }
错误原因分析
- 取消信号未关联到旧任务:旧任务使用的是自己创建的新
CancellationTokenSource的Token,而新任务取消的是更早的TokenSource,导致旧任务无法收到取消信号,逻辑完全错位。 - 异常捕获范围不足:仅捕获
TaskCanceledException,但OperationCanceledException是其基类,部分取消场景(如手动调用token.ThrowIfCancellationRequested())会抛出基类异常,无法被捕获,导致旧任务不会返回null。 - 资源未释放:频繁创建
CancellationTokenSource但未调用Dispose,会造成内存泄漏。 - 语法错误:使用关键字
out作为变量名,代码无法正常编译。 - 测试代码不规范:
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
相关产品推荐
相关产品推荐

