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
相关产品推荐
相关产品推荐

