如何在C# Socket场景中正确避免ObjectDisposedException?
如何避免Socket异步回调中的ObjectDisposedException
首先咱们先搞懂为什么会出现这个错误:当你调用Dispose()关闭_serverSocket之后,之前通过BeginAccept发起的异步操作的回调OnAcceptConnection可能还在排队执行,这时候在回调里调用EndAccept,就会因为Socket已经被释放而抛出ObjectDisposedException。你原来写的if (_serverSocket == null)检查完全没用——因为Dispose()里只是关闭了Socket,并没有把_serverSocket变量置为null,它还是指向那个已经被释放的对象实例。
接下来咱们一步步按照最佳实践修正这个问题,都是适合自学C#的新手理解的方案:
1. 添加线程安全的已释放状态标志
异步回调和Dispose()可能在不同线程执行,所以需要一个能跨线程同步的状态标记。添加一个volatile修饰的布尔字段(volatile保证多线程下变量的可见性,避免线程读到过期的状态):
internal sealed class SocketHandler : IDisposable { private static readonly ILogger Logger = LogManager.GetCurrentClassLogger(); private readonly Socket _serverSocket; private volatile bool _isDisposed; // 新增:标记对象是否已释放 // ... 其他原有代码 }
2. 完善Dispose方法,保证状态同步
在Dispose()里先标记_isDisposed为true,再关闭Socket,同时加锁避免和回调操作并发冲突:
public void Dispose() { if (_isDisposed) return; // 避免重复调用Dispose lock (_serverSocket) { // 加锁防止和回调线程的并发操作 _isDisposed = true; _serverSocket.Close(); } }
3. 在回调中优先检查已释放状态
在OnAcceptConnection的最开始就检查_isDisposed,如果对象已经释放,直接终止流程,不要执行后续操作:
private void OnAcceptConnection(IAsyncResult asyncResult) { // 先检查是否已释放,直接终止无效流程 if (_isDisposed) { return; } try { // 原有业务代码... var client = ((Socket) asyncResult.AsyncState).EndAccept(asyncResult); // ... 后续连接判断、玩家添加逻辑 } catch (SocketException socketException) { Logger.Fatal(socketException, "Failed to accept socket connection."); } catch (ObjectDisposedException ex) { // 兜底捕获:多线程环境下可能出现检查状态后,对象刚好被释放的竞态条件 Logger.Warn(ex, "Attempted to accept connection on a disposed socket."); } finally { // 只有当对象未释放时,才继续监听新连接 if (!_isDisposed) { _serverSocket.BeginAccept(OnAcceptConnection, _serverSocket); } } }
为什么这是最佳实践?
- 提前终止无效流程:通过状态标志快速过滤掉已释放后的回调,避免不必要的操作
- 线程安全的资源释放:用锁保证标记状态和关闭Socket的原子性,防止并发冲突
- 兜底异常捕获:多线程环境下可能出现“检查状态后,对象刚好被释放”的竞态条件,捕获
ObjectDisposedException作为最后一道防线 - 停止后续异步操作:在finally块里只在未释放时重新发起
BeginAccept,避免Dispose之后还继续监听新连接
给自学C#的你的小建议
- 实现
IDisposable的类,记得遵循Dispose模式(如果类包含非托管资源需要更复杂的实现,这里Socket是托管资源,当前简化版足够) - 异步操作的回调一定要考虑对象的生命周期,尤其是类被释放时的边界情况
- 多线程环境下的变量访问,要注意可见性和原子性,
volatile和lock是新手最容易掌握的解决手段
内容的提问来源于stack exchange,提问作者ropuxil
相关产品推荐
相关产品推荐

