在C#多线程开发场景中,如果一个实现了IDisposable接口的对象被多个线程同时持有并可能调用Dispose方法,重复释放资源会导致不可预期的错误,比如释放已经释放的句柄、触发对象状态异常等。因此需要设计合理的逻辑,保证Dispose方法在并发场景下只执行一次。

为什么并发下多次Dispose会有问题
标准的IDisposable实现通常包含释放托管资源和非托管资源的逻辑,很多实现没有做并发防护,当多个线程同时进入Dispose方法时,可能会出现以下情况:
- 非托管资源被重复释放,导致系统API调用异常
- 释放过程中修改的对象状态被多个线程同时操作,引发数据竞争
- 已经置为释放状态的对象被再次操作,抛出空引用异常
方案一:使用Interlocked实现原子状态标记
这是最轻量的实现方式,通过原子操作标记对象是否已经被释放,适合不需要复杂同步逻辑的场景。核心思路是用一个int类型的字段标记释放状态,0表示未释放,1表示已释放,通过Interlocked.CompareExchange保证状态修改的原子性。
using System;
using System.Threading;
public class ConcurrentDisposable : IDisposable
{
// 0: 未释放 1: 已释放
private int _disposed = 0;
public void Dispose()
{
// 原子操作:如果当前值是0,就替换为1,返回原始值
// 只有第一个将0改为1的线程会拿到0的原始值,执行释放逻辑
if (Interlocked.CompareExchange(ref _disposed, 1, 0) == 0)
{
DisposeCore();
}
}
private void DisposeCore()
{
// 这里写实际的资源释放逻辑
Console.WriteLine("执行资源释放逻辑");
}
// 供内部检查是否已释放的方法
public bool IsDisposed => Volatile.Read(ref _disposed) == 1;
}
这种方式的优势是性能高,没有锁的开销,适合高频调用的场景。需要注意的是,_disposed字段的读取要用Volatile.Read保证可见性,避免线程读取到过期的缓存值。
方案二:使用lock语句加锁保护
如果释放逻辑本身包含需要同步的操作,或者需要兼容旧有的复杂Dispose逻辑,可以使用lock加锁的方式保证同一时间只有一个线程执行释放逻辑。
using System;
public class LockConcurrentDisposable : IDisposable
{
private readonly object _disposeLock = new object();
private bool _disposed = false;
public void Dispose()
{
lock (_disposeLock)
{
if (_disposed)
{
return;
}
_disposed = true;
DisposeCore();
}
}
private void DisposeCore()
{
// 实际的资源释放逻辑
Console.WriteLine("执行资源释放逻辑");
}
public bool IsDisposed => _disposed;
}
这种方式的优点是逻辑直观,容易理解,适合释放逻辑较复杂的场景。缺点是有锁的开销,高并发场景下性能略低于Interlocked方案。
两种方案的对比
我们可以通过下表对比两种方案的适用场景:
| 方案 | 性能 | 适用场景 | 复杂度 |
|---|---|---|---|
| Interlocked原子标记 | 高 | 释放逻辑简单、高频调用场景 | 低 |
| lock加锁保护 | 中等 | 释放逻辑复杂、需要同步其他操作 | 低 |
注意事项
无论使用哪种方案,都需要注意以下几点:
- Dispose方法应该是幂等的,即使被多次调用也不会产生副作用,上述方案已经保证了这一点
- 如果类包含析构函数(Finalize方法),需要在析构函数中也加入状态检查,避免析构时重复释放
- 不要在Dispose方法中抛出异常,否则可能导致其他线程的Dispose逻辑中断
- 如果对象需要被传递给多个线程使用,建议在文档中明确说明该对象的Dispose线程安全特性
提示:如果使用的是.NET Core及以上版本,还可以考虑使用System.Threading.ResetEvent或者CancellationToken等机制配合实现,但核心思路都是保证释放状态的唯一性和操作的原子性。
IDisposable并发控制Interlocked原子操作Dispose模式修改时间:2026-06-21 20:48:27