Unity C# TCP客户端断开时监听线程抛出ThreadAbortException问题
业务实现背景
项目中采用Thread搭配TcpClient实现与TCP服务端的读写通信,设计逻辑如下:
- 客户端写入操作触发时初始化TCP连接
- 待服务端响应校验完成后立即执行TCP断开逻辑
- 通过短连接复用机制保障多异步用户场景下的套接字可用性,单客户端调用间隔为5-10秒
实际运行时,在线程释放阶段执行断开操作会抛出一系列错误。
初始问题:线程中止触发读写异常
触发场景
调用DisconnectFromTCPServer()执行clientReceiveThread.Abort()时,监听委托ListenForData()内using (NetworkStream ... )代码块的while ((length = stream.Read ... )逻辑抛出如下异常:
ThreadAbortException: Thread was being aborted.
System.ComponentModel.Win32Exception..ctor (System.Int32 error) (at <1720f652f31145af877fbb9e0d1ba65c>:0)
System.Net.Sockets.SocketException..ctor (System.Net.Sockets.SocketError socketError) (at <1720f652f31145af877fbb9e0d1ba65c>:0)
System.Net.Sockets.Socket.Receive (System.Byte[] buffer, System.Int32 offset, System.Int32 size, System.Net.Sockets.SocketFlags socketFlags) (at <1720f652f31145af877fbb9e0d1ba65c>:0)
System.Net.Sockets.NetworkStream.Read (System.Byte[] buffer, System.Int32 offset, System.Int32 size) (at <1720f652f31145af877fbb9e0d1ba65c>:0)
Rethrow as IOException: Unable to read data from the transport connection: Thread was being aborted..
System.Net.Sockets.NetworkStream.Read (System.Byte[] buffer, System.Int32 offset, System.Int32 size) (at <1720f652f31145af877fbb9e0d1ba65c>:0)
初步排查结论
DisconnectFromTCPServer()中设置的isAlive标志未被ListenForData()委托正确识别,是触发异常的直接原因。该逻辑在新增NewData校验逻辑(主线程调用public Move AIResponse()且NewData为true时将serverMessage置为null)前可正常运行。
初始实现代码
private void ConnectToTcpServer() { try { Debug.Log("CONNECTING TO SERVER..."); clientReceiveThread = new Thread(new ThreadStart(ListenForData)); clientReceiveThread.IsBackground = true; clientReceiveThread.Start(); isAlive = true; } catch (Exception e) { Debug.Log("On client connect exception " + e); } } private void DisconnectFromTcpServer() { isAlive = false; if (socketConnection.Connected) ((IDisposable)socketConnection).Dispose(); StartCoroutine(ServerDisconnectedCoroutine(() => !socketConnection.Connected)); if (clientReceiveThread.IsAlive) clientReceiveThread.Abort(); clientReceiveThread.Join(); ServerConnected = false; } private IEnumerator ServerDisconnectedCoroutine(System.Func<bool> socketDisconnected) { yield return new WaitUntil(socketDisconnected); } public void ListenForData() { // SWITCH PORTS ON EACH CONNECT - BALANCE THE LOAD // WRAP IN COUNTER port = port == 12345 ? 12344 : 12345; try { socketConnection = new TcpClient("65.21.xxx.xxx", 12345); Byte[] bytes = new Byte[1024]; Thread.Sleep(100); while (isAlive) { // Test socket connection if (!socketConnection.Connected) { Debug.Log("DISCONNECTED LISTEN"); serverTimeout(); return; } else ServerConnected = true; // Get a stream object for reading using (NetworkStream stream = socketConnection.GetStream()) { int length; // Read incoming stream into byte arrary. while ((length = stream.Read(bytes, 0, bytes.Length)) != 0) { var incomingData = new byte[length]; Array.Copy(bytes, 0, incomingData, 0, length); // Convert byte array to string message. serverMessage = Encoding.UTF8.GetString(incomingData); Debug.Log("server message received as: " + serverMessage); // ORIGINAL NewData = true; } stream.Close(); } } } catch (SocketException socketException) { Debug.Log("Socket exception: " + socketException); } catch (ThreadAbortException threadException) { Debug.Log("ThradException: " + threadException); } } public bool IfNewData() { if (serverMessage != null && !NewData) aiDataFromServer.LoadString(serverMessage); if (aiDataFromServer.type != "Error") NewData = true; return NewData; } public Move AIResponse() { NewData = false; // ORIGINAL - serverMessage ! set to null serverMessage = null; DisconnectFromTcpServer(); return aiDataFromServer.moveData; }
已尝试修复方案
- 延迟
Thread.Abort()调用时机 - 主动关闭NetworkStream与套接字
以上方案均无法阻止监听委托尝试读取数据,触发异常时控制台已打印服务端响应,后续无数据传输,初始阶段未定位到根因。
衍生问题:初始异常修复后出现非预期套接字断开
参考相关方案排查调整代码后,ThreadAbortException错误已修复,但代码调整导致SocketConnection出现异常行为:执行完stream.CanRead、stream.DataAvailable判断与stream.Close()后,循环回到顶层判定!serverConnection.Connected,触发Debug.Log("DISCONNECTED LISTEN")逻辑导致服务端重连。此时主线程已调用AIResponse(),连接与线程本就会被关闭,但暂未定位到流读取完成后套接字连接被主动关闭的原因。
修改后代码
public void ListenForData() { // SWITCH PORTS ON EACH CONNECT - BALANCE THE LOAD // WRAP IN COUNTER port = port == 12345 ? 12344 : 12345; try { socketConnection = new TcpClient("65.21.xxx.xxx", 12345); Byte[] bytes = new Byte[1024]; while (isAlive) { Debug.Log("IS ALIVE"); // Test socket connection if (!socketConnection.Connected && connectionRequired) { Debug.Log("DISCONNECTED LISTEN"); serverTimeout(); return; } else ServerConnected = true; using (NetworkStream stream = socketConnection.GetStream()) { if (stream.CanRead) { int _length; do { _length = stream.Read(bytes, 0, bytes.Length); var _incomingData = new byte[_length]; Array.Copy(bytes, 0, _incomingData, 0, _length); serverMessage = Encoding.UTF8.GetString(_incomingData); Debug.Log("server message received as: " + serverMessage); // ALLOW REFERENCES TO BE UPDATED if (!stream.DataAvailable) Thread.Sleep(1); } while (stream.DataAvailable); } stream.Close(); Debug.Log("STREAM CLOSED"); } } } catch (SocketException socketException) { Debug.Log("Socket exception: " + socketException); } catch (ThreadAbortException threadException) { Debug.Log("ThradException: " + threadException); } }
该衍生问题虽不影响核心流程,但不符合预期,需要对应的原因分析与优化方案。
内容的提问来源于stack exchange,提问作者EAYAJB

