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

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)
    {
        // 此处可添加拖放逻辑
    }
}

额外说明

  1. 给代码加了try-catch捕获Socket异常,避免接收出错导致程序崩溃;
  2. 补充了OnDestroy里关闭Socket的逻辑,防止资源泄漏;
  3. 修正了你代码里的拼写错误(socketsFunctinality少了一个i);
  4. 用OnAssignCards?.Invoke()代替直接Invoke,避免没有订阅者时抛出空引用异常;
  5. 一定要在CardsReceived回调里再次调用BeginReceive,否则Socket只能接收一次消息。

内容的提问来源于stack exchange,提问作者Tarek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:54:33