多线程场景下如何实现前序任务的线程安全取消?
线程安全的任务取消方案(针对多线程触发的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
相关产品推荐
相关产品推荐

