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

Unity中WebSocket.OnOpen事件无法将动作入队至ConcurrentQueue问题

问题:WebSocket事件中入队的动作未在Unity主线程执行

我尝试在WebSocket.OnOpen事件中将回调入队到动作队列:

ws.OnOpen += (sender, args) =>
{
    _actions.Enqueue(() => OnClientConnected());
    //OnClientConnected?.Invoke();
    //OnConnectionStateChange?.Invoke();
    //RequestValidation();
};

但Update方法里的Debug.Log(_actions.Count)始终输出0,动作根本没被加入队列:

private void Update()
{
    Debug.Log(_actions.Count);
    
    while (_actions.TryDequeue(out var action))
    {
        Debug.Log("is this even working?");
        action?.Invoke();
    }
}

这么做是为了后续能从事件中启动协程(比如重复发送Ping),而协程必须在Unity主线程运行。

问题根源

WebSocketSharp的事件回调是在子线程触发的,你的单例模式存在实例不一致问题:

  • 第一次访问UnityMultiNetworking.Instance时,会执行_instance = new UnityMultiNetworking(),这个实例没有挂载到GameObject上,没有MonoBehaviour生命周期(不会执行Update)。
  • 之后调用CreateInstance()时,又会创建一个挂载在GameObject上的实例,这个实例的Update会被Unity调用,但它的_actions队列和子线程里访问的那个实例的队列完全独立。
  • 子线程的OnOpen事件往无Mono的实例队列里加动作,而你在有Mono的实例的Update里查看另一个队列,自然看不到数据。

修复方案

1. 修正单例模式,确保只有一个有效实例

修改Instance的getter逻辑,禁止通过构造函数创建无Mono的实例:

private static UnityMultiNetworking _instance;

public static UnityMultiNetworking Instance
{
    get
    {
        if (_instance == null)
        {
            // 直接创建带Mono的实例,而非new无Mono的对象
            GameObject obj = new GameObject("UnityNetworking");
            _instance = obj.AddComponent<UnityMultiNetworking>();
            DontDestroyOnLoad(obj);
        }
        return _instance;
    }
    private set { }
}

// 保留CreateInstance方法,内部直接返回单例即可
public static UnityMultiNetworking CreateInstance()
{
    return Instance;
}

同时同步Awake逻辑,避免重复实例:

private void Awake()
{
    if (_instance == null)
    {
        _instance = this;
        DontDestroyOnLoad(gameObject);
    }
    else if (_instance != this)
    {
        Destroy(gameObject);
    }
}

2. 确保WebSocket事件访问正确实例

在CreateConnection里,明确让事件回调指向当前实例的队列:

private void CreateConnection()
{
    ws = new WebSocket(connectionURL);

    ws.OnOpen += (sender, args) =>
    {
        // 明确使用当前实例的队列
        this._actions.Enqueue(() => 
        {
            OnClientConnected?.Invoke();
            // 此处可直接启动协程,比如:
            // StartCoroutine(PingCoroutine());
        });
        OnConnectionStateChange?.Invoke();
        RequestValidation();
    };

    // 其他事件回调同理...
}

3. 可选:添加日志确认入队状态

在OnOpen回调里加日志,验证子线程是否成功入队:

ws.OnOpen += (sender, args) =>
{
    Debug.Log($"子线程入队动作,当前队列数量:{_actions.Count}");
    _actions.Enqueue(() => 
    {
        Debug.Log("主线程执行动作");
        OnClientConnected?.Invoke();
    });
};

额外优化:协程启动示例

因为入队的Action会在主线程的Update中执行,所以可以直接在Action内启动协程:

private IEnumerator PingCoroutine()
{
    while (IsConnected)
    {
        SendPing();
        yield return new WaitForSeconds(5f);
    }
}

// 在OnOpen的入队动作里启动协程
ws.OnOpen += (sender, args) =>
{
    _actions.Enqueue(() => 
    {
        OnClientConnected?.Invoke();
        StartCoroutine(PingCoroutine());
    });
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 01:27:35