C# TCPListener/TcpClient出现ObjectDisposedException问题求助
首先,你的代码抛出ObjectDisposedException和IOException的核心原因是NetworkStream的生命周期被错误地多次释放,具体来说有两个关键问题:
1. StreamWriter的using块默认会关闭底层NetworkStream
在你的SendMsg方法中,你用using包裹了StreamWriter,而默认情况下,StreamWriter的Dispose方法会自动关闭它所依赖的底层NetworkStream。当这个using块执行完毕,NetworkStream就被释放了,而你的KeepAlive方法还在使用同一个TcpClient的NetworkStream,后续的读写操作自然会抛出异常。
同样,如果KeepAlive方法中的using (NetworkStream networkStream = tcpClient.GetStream())先执行完毕(比如循环因异常退出),也会释放这个流,导致SendMsg中再次调用GetStream()时拿到已释放的对象。
2. async void方法的生命周期难以控制
你的KeepAlive和AcceptConnections都是async void方法,这种方法无法被等待,也很难捕获全局异常,一旦内部出现未处理的异常,可能会直接终止整个应用,同时也会导致using块提前执行,释放资源。
修复方案
方案一:让StreamWriter不关闭底层流
修改SendMsg方法,创建StreamWriter时指定leaveOpen: true,这样using块释放StreamWriter时不会关闭NetworkStream:
public async Task SendMsg(String message) { // 改用async Task代替async void try { NetworkStream networkStream = tcpClient.GetStream(); // 设置leaveOpen: true,避免关闭底层流 using (var writer = new StreamWriter(networkStream, leaveOpen: true)) { await writer.WriteLineAsync(message); await writer.FlushAsync(); // 手动刷新,确保数据发送 Console.WriteLine("msg sent"); }; } catch (Exception e) { Console.WriteLine("SendMsg异常: " + e.Message); // 不要吞异常,便于调试 } }
方案二:统一管理NetworkStream的生命周期
不要在多个方法中分别获取NetworkStream,而是在AcceptConnections中拿到TcpClient后,一次性获取NetworkStream,并传递给KeepAlive和SendMsg方法,确保整个连接生命周期内复用同一个流,只在连接断开时才释放它:
public async Task AcceptConnections() { // 改用async Task while (true) { while (clientsConnected <= maxConnections) { try { var tcpClient = await tcpListener.AcceptTcpClientAsync(); var networkStream = tcpClient.GetStream(); // 启动KeepAlive任务,后台运行 _ = KeepAlive(tcpClient, networkStream); } catch (Exception e) { Console.WriteLine("TOP EXCEPTION :: " + e); } clientsConnected++; Console.WriteLine("SERVER Clients connected: " + clientsConnected); } await Task.Delay(100); // 避免空循环占用CPU } } public async Task KeepAlive(TcpClient tcpClient, NetworkStream networkStream) { // 改用async Task bool clientConnected = true; // 不再用using包裹networkStream,而是在连接断开时手动释放 using (var reader = new StreamReader(networkStream, leaveOpen: true)) using (var writer = new StreamWriter(networkStream, leaveOpen: true)) { writer.AutoFlush = true; char keepalive = '0'; while (clientConnected) { try { await writer.WriteLineAsync(keepalive); string dataFromClient = await reader.ReadLineAsync(); Console.WriteLine("Server: " + dataFromClient); await Task.Delay(500); // 用Task.Delay代替Thread.Sleep,避免阻塞异步线程 } catch (IOException e) { Console.WriteLine("连接异常: " + e.Message); clientConnected = false; } catch(ObjectDisposedException e) { clientConnected = false; clientsConnected--; Console.WriteLine("客户端断开连接"); } catch (Exception e) { Console.WriteLine("未知异常: " + e.Message); clientConnected = false; } } // 连接断开后,释放资源 networkStream.Dispose(); tcpClient.Dispose(); } }
额外注意事项
- 永远不要吞掉异常(你的代码里有
catch (Exception e) { }),至少要打印异常信息,否则调试时根本不知道哪里出问题。 - 用
Task.Delay代替Thread.Sleep,因为Thread.Sleep会阻塞当前线程,而异步代码中应该用非阻塞的延迟。 - 避免使用
async void,除非是事件处理方法,否则一律用async Task,这样可以更好地控制任务生命周期和捕获异常。 - 你的
Main方法如果要调用异步方法,应该改成async Task Main(C# 7.1+支持),比如:
static async Task Main(string[] args) { server.Listen(); _ = server.AcceptConnections(); // 后台运行接受连接的任务 client.ConnectToServer(); for (int i = 0; i < 5; i++) { await Task.Delay(3000); await server.SendMsg("SENT MSG"); } await Console.In.ReadLineAsync(); // 改用异步读取 }
内容的提问来源于stack exchange,提问作者Ian Gleeson

