You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity中按键输入无法持续识别的问题排查与解决方案咨询

Unity TCP通信中按键输入失效问题排查与解决

问题描述

编写Unity代码通过TCP/IP接收信息并更新物体位置,同时添加了空格键召唤临时物体的功能。程序启动初期按键输入正常,但约10秒后无法检测到按键输入;物体位置仍能根据接收的信息更新,说明Update函数仍在执行。

原因分析

  1. 子线程无休眠导致CPU占用过高:ReceiveDataThread中的循环持续无间隔运行,占用大量CPU资源,抢占主线程执行时间,导致Update函数无法及时处理按键输入。
  2. 主线程执行网络IO引发阻塞:ProcessReceivedData在主线程中调用client.GetStream().Write()发送响应,网络操作可能阻塞主线程,使按键检测逻辑无法正常执行。
  3. 跨线程对象未加锁保护:client对象在子线程和主线程同时访问,存在线程安全隐患,可能引发未知卡顿。
  4. 线程终止方式不安全:使用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,提问作者이건웅

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 22:54:54