Unity中按键输入无法持续识别的问题排查与解决方案咨询
Unity TCP通信中按键输入失效问题排查与解决
问题描述
编写Unity代码通过TCP/IP接收信息并更新物体位置,同时添加了空格键召唤临时物体的功能。程序启动初期按键输入正常,但约10秒后无法检测到按键输入;物体位置仍能根据接收的信息更新,说明Update函数仍在执行。
原因分析
- 子线程无休眠导致CPU占用过高:
ReceiveDataThread中的循环持续无间隔运行,占用大量CPU资源,抢占主线程执行时间,导致Update函数无法及时处理按键输入。 - 主线程执行网络IO引发阻塞:
ProcessReceivedData在主线程中调用client.GetStream().Write()发送响应,网络操作可能阻塞主线程,使按键检测逻辑无法正常执行。 - 跨线程对象未加锁保护:
client对象在子线程和主线程同时访问,存在线程安全隐患,可能引发未知卡顿。 - 线程终止方式不安全:使用
Thread.Abort()强制终止线程,易导致资源泄漏或线程状态异常。
解决方案
1. 给子线程添加休眠,降低CPU占用
在子线程的循环中添加短暂休眠,减少CPU消耗,避免抢占主线程资源。
2. 将网络写操作移至子线程
把发送"OK"响应的逻辑移到子线程中,或通过队列传递响应任务,避免主线程执行网络IO操作。
3. 对跨线程访问的对象加锁
确保client等跨线程访问的对象在操作时受到锁保护,避免线程冲突。
4. 优雅终止线程
替换Thread.Abort(),通过设置running=false后等待线程自然结束。
修改后的完整代码
using System.Collections; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Text; using UnityEngine; using System.Threading; using Newtonsoft.Json; public class TagObjectCreator2 : MonoBehaviour { public string connectionIP = "127.0.0.1"; public int connectionPort = 25001; IPAddress localAdd; TcpListener listener; TcpClient client; bool running; public GameObject cubePrefab; public GameObject cylinderPrefab; public GameObject elsePrefab; public GameObject tempPrefab; Dictionary<string, GameObject> gameObjects = new Dictionary<string, GameObject>(); Thread receiveThread; private Queue<string> dataQueue = new Queue<string>(); // 响应队列,由子线程处理发送逻辑 private Queue<string> responseQueue = new Queue<string>(); private object clientLock = new object(); private void Start() { receiveThread = new Thread(ReceiveDataThread); receiveThread.Start(); } private void Update() { if (Input.GetKeyDown(KeyCode.Space)) { Debug.Log("Key가 눌렸습니다."); Instantiate(tempPrefab, transform.position, Quaternion.identity); } // 处理接收的数据 string dataReceived = null; lock (dataQueue) { if (dataQueue.Count > 0) { dataReceived = dataQueue.Dequeue(); } } if (dataReceived != null) { ProcessReceivedData(dataReceived); } } // TCP通信子线程 private void ReceiveDataThread() { localAdd = IPAddress.Parse(connectionIP); listener = new TcpListener(IPAddress.Any, connectionPort); listener.Start(); client = listener.AcceptTcpClient(); running = true; while (running) { // 处理接收的数据 if (client.Available > 0) { NetworkStream nwStream = client.GetStream(); byte[] buffer = new byte[client.ReceiveBufferSize]; int bytesRead = nwStream.Read(buffer, 0, client.ReceiveBufferSize); string dataReceived = Encoding.UTF8.GetString(buffer, 0, bytesRead); Debug.Log("Received data: " + dataReceived); lock (dataQueue) { dataQueue.Enqueue(dataReceived); } } // 处理发送响应 string response = null; lock (responseQueue) { if (responseQueue.Count > 0) { response = responseQueue.Dequeue(); } } if (response != null) { lock (clientLock) { if (client.Connected) { NetworkStream nwStream = client.GetStream(); byte[] responseBytes = Encoding.UTF8.GetBytes(response); nwStream.Write(responseBytes, 0, responseBytes.Length); } } } // 添加休眠,降低CPU占用 Thread.Sleep(10); } // 优雅关闭资源 lock (clientLock) { if (client != null) { client.Close(); client = null; } } listener.Stop(); } private void ProcessReceivedData(string dataReceived) { List<DataObject> dataObjects = JsonConvert.DeserializeObject<List<DataObject>>(dataReceived); HashSet<string> receivedNames = new HashSet<string>(); foreach (DataObject dataObject in dataObjects) { receivedNames.Add(dataObject.Name); Vector3 receivedPos = new Vector3(dataObject.Position.x, dataObject.Position.y, dataObject.Position.z); if (gameObjects.ContainsKey(dataObject.Name)) { gameObjects[dataObject.Name].transform.position = receivedPos; } else { GameObject newObj; if (dataObject.ObjectType == "Table") { newObj = Instantiate(cubePrefab, receivedPos, Quaternion.identity); } else if (dataObject.ObjectType == "robotic vacuum") { newObj = Instantiate(cylinderPrefab, receivedPos, Quaternion.identity); } else { newObj = Instantiate(elsePrefab, receivedPos, Quaternion.identity); } Debug.Log(dataObject.Name + "이/가 추가되었습니다."); gameObjects.Add(dataObject.Name, newObj); } } List<string> namesToRemove = new List<string>(); foreach (KeyValuePair<string, GameObject> pair in gameObjects) { if (!receivedNames.Contains(pair.Key)) { Debug.Log(pair.Key + "이/가 삭제되었습니다."); Destroy(pair.Value); namesToRemove.Add(pair.Key); } } foreach (string name in namesToRemove) { gameObjects.Remove(name); } // 将响应放入队列,由子线程发送 lock (responseQueue) { responseQueue.Enqueue("OK"); } } private void OnApplicationQuit() { running = false; if (receiveThread != null && receiveThread.IsAlive) { // 优先等待线程自然结束,超时后再强制终止 receiveThread.Join(1000); if (receiveThread.IsAlive) { receiveThread.Abort(); } } } public class DataObject { public string Name { get; set; } public string ObjectType { get; set; } public PositionData Position { get; set; } } public class PositionData { public float x { get; set; } public float y { get; set; } public float z { get; set; } } }
关键修改点说明
- 添加
responseQueue队列,将发送响应的逻辑移到子线程处理,避免主线程阻塞。 - 在子线程循环末尾添加
Thread.Sleep(10),降低CPU占用。 - 对
client对象的访问添加clientLock锁,确保线程安全。 - 修改
OnApplicationQuit中的线程终止逻辑,优先等待线程自然结束,仅在超时后才强制终止。
内容的提问来源于stack exchange,提问作者이건웅
相关产品推荐
相关产品推荐

