使用using处置CancellationTokenSource是否安全?代码实现是否正确?
这段代码存在线程安全隐患,你担心的问题确实可能发生!
你顾虑的场景完全是成立的——这是一个典型的竞态条件问题:
- 在
CancelOperation()方法里,你刚做完_myCts != null的检查,此时它确实是非空状态; - 就在这一瞬间,
RunAsync()里的using块刚好执行完毕,_myCts被自动释放并置为null; - 接着你执行
_myCts.Cancel(),此时_myCts已经被释放,直接会抛出ObjectDisposedException。
怎么修复这个问题?
我们需要确保对_myCts的检查和取消操作是原子性的,不会被其他线程打断。这里给你两种常用的靠谱方案:
方案1:使用锁(Lock)实现同步
给_myCts的读写操作加锁,保证检查和Cancel操作是一个不可分割的整体:
private readonly object _ctsLock = new object(); CancellationTokenSource _myCts; private async Task RunAsync() { using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token1, token2)) { lock (_ctsLock) { _myCts = cts; } try { await DoWork(); } finally { lock (_ctsLock) { _myCts = null; } } } } public void CancelOperation() { lock (_ctsLock) { _myCts?.Cancel(); } }
把_myCts的赋值、清空和取消操作都放在锁的保护下,就能彻底避免竞态条件的发生。
方案2:使用Interlocked原子操作(无锁方案)
如果对性能有要求,不想用锁,也可以用Interlocked类来原子地操作_myCts变量:
CancellationTokenSource _myCts; private async Task RunAsync() { using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token1, token2)) { // 原子替换旧的_myCts值为新创建的cts Interlocked.Exchange(ref _myCts, cts); try { await DoWork(); } finally { // 只有当前cts还是_myCts的引用时,才将其置为null Interlocked.CompareExchange(ref _myCts, null, cts); } } } public void CancelOperation() { // 原子获取当前_myCts并将其置为null,再执行Cancel var cts = Interlocked.Exchange(ref _myCts, null); cts?.Cancel(); // 注:这里不需要手动Dispose,因为RunAsync里的using块会负责释放 }
这种方式利用原子操作避免了锁的开销,适合对性能敏感的场景。
额外注意点
- 不管用哪种方案,都不要在
Cancel之后直接访问_myCts,因为它大概率已经被释放; - 一定要确保
DoWork方法内部正确响应CancellationToken,否则Cancel操作根本不会生效。
内容的提问来源于stack exchange,提问作者Cadmi
相关产品推荐
相关产品推荐

