Unity C# Socket回调触发事件可打印日志但无法修改GameObject属性
解决Unity中Socket异步回调无法修改UI/Transform的问题
你猜的没错,这确实是线程问题导致的!咱们一步步拆解原因和解决方案:
问题根源
Unity的所有核心API(包括UI操作、Transform修改、GameObject状态变更)都必须在主线程中执行,这是Unity的线程安全机制决定的。
你用BeginReceive发起的异步Socket接收,它的回调函数CardsReceived是运行在.NET的线程池后台线程里的,并非Unity的主线程。所以在这个回调触发的事件里修改gameObject.transform.scale,Unity根本不会处理这个变更——复杂场景下甚至可能抛出线程安全异常。
而你把OnAssignCards.Invoke放到Start函数里时,Start是Unity主线程执行的生命周期函数,所以修改自然能生效。
解决方案:把操作切回主线程
我们可以给socketsFunctionality类加一个主线程任务队列,把需要在主线程执行的操作(比如触发事件)放到队列里,然后在Unity主线程的Update函数里逐个执行这些任务。
修改后的socketsFunctionality代码
using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using UnityEngine; public class socketsFunctionality : MonoBehaviour { Socket socket; public const int BufferSize = 16384; public byte[] buffer = new byte[BufferSize]; public delegate void OnAssignCardsDelegate(listOfCards cards); public OnAssignCardsDelegate OnAssignCards; // 存储需要在主线程执行的任务 private readonly Queue<Action> _mainThreadTaskQueue = new Queue<Action>(); // 锁对象,保证多线程操作队列的安全性 private readonly object _queueLock = new object(); void Start() { IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse("192.168.0.2"), 80); Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); client.BeginConnect(remoteEP, ConnectCallback, client); } private void ConnectCallback(IAsyncResult ar) { socket = (Socket)ar.AsyncState; socket.EndConnect(ar); // 继续监听下一次接收(原代码遗漏这一步,会只接收一次消息) socket.BeginReceive(buffer, 0, BufferSize, 0, CardsReceived, socket); } private void CardsReceived(IAsyncResult ar) { Socket s = (Socket)ar.AsyncState; try { int bytesRead = s.EndReceive(ar); if (bytesRead > 0) { var cards = new listOfCards(); // 把触发事件的操作放到主线程队列 lock (_queueLock) { _mainThreadTaskQueue.Enqueue(() => OnAssignCards?.Invoke(cards)); } } // 持续监听下一次接收,保证Socket能不断接收消息 s.BeginReceive(buffer, 0, BufferSize, 0, CardsReceived, s); } catch (SocketException ex) { Debug.LogError($"Socket接收出错: {ex.Message}"); } } void Update() { // 在主线程处理队列里的任务 lock (_queueLock) { while (_mainThreadTaskQueue.Count > 0) { _mainThreadTaskQueue.Dequeue().Invoke(); } } } void OnDestroy() { // 销毁时关闭Socket,避免资源泄漏 socket?.Close(); socket?.Dispose(); } }
修改后的tableScript代码(修正拼写错误)
using UnityEngine; using UnityEngine.EventSystems; public class tableScript : MonoBehaviour, IDropHandler { void Start() { // 修正拼写错误:socketsFunctinality → socketsFunctionality GetComponent<socketsFunctionality>().OnAssignCards += CardsReceived; } public void CardsReceived(listOfCards cards) { Debug.Log("triggered"); // 这里的修改现在会正常生效,因为是在主线程执行的 gameObject.transform.localScale = new Vector3(100f, 100f, 100f); } // 实现IDropHandler接口的必要方法(原代码遗漏) public void OnDrop(PointerEventData eventData) { // 此处可添加拖放逻辑 } }
额外说明
- 给代码加了
try-catch捕获Socket异常,避免接收出错导致程序崩溃; - 补充了
OnDestroy里关闭Socket的逻辑,防止资源泄漏; - 修正了你代码里的拼写错误(
socketsFunctinality少了一个i); - 用
OnAssignCards?.Invoke()代替直接Invoke,避免没有订阅者时抛出空引用异常; - 一定要在
CardsReceived回调里再次调用BeginReceive,否则Socket只能接收一次消息。
内容的提问来源于stack exchange,提问作者Tarek
相关产品推荐
相关产品推荐

