Unity C#多线程依次Join致帧率下降,寻求并行Join或优化方案
嗨,我来帮你搞定这个帧率掉帧的问题!核心问题就是你现在在LateUpdate里串行等待12个线程,每个最多等10ms,总耗时最多能到120ms——这远远超过了60FPS单帧16ms左右的时间预算,不掉帧才怪。下面给你几个可行的解决方案,一步步帮你把帧率拉回来:
1. 最直接的改进:并行等待所有线程
你完全可以同时等待所有线程完成,而不是挨个等。用WaitHandle.WaitAll就能实现,这样总等待时间最多就是单个线程的超时时间(10ms),而不是累加。
具体实现代码
首先定义需要的变量:
private List<ManualResetEvent> _threadEvents = new List<ManualResetEvent>(); private List<string> _threadResults = new List<string>(); private List<string> _allPorts = new List<string>(); // 提前存好12个端口 void Start() { // 初始化12个端口,比如: _allPorts.Add("port_01"); _allPorts.Add("port_02"); // ... 剩下10个端口 }
然后修改Update和LateUpdate:
void Update() { // 清空上一帧的事件和结果 _threadEvents.Clear(); _threadResults.Clear(); foreach (var port in _allPorts) { var resetEvent = new ManualResetEvent(false); _threadEvents.Add(resetEvent); // 占位结果,后面线程会修改对应索引的值 _threadResults.Add(null); int resultIndex = _threadResults.Count - 1; // 启动线程 new Thread(() => { try { _threadResults[resultIndex] = ReadMessage(port); } finally { // 线程完成后触发信号 resetEvent.Set(); } }).Start(); } } void LateUpdate() { // 等待所有线程完成,最多等10ms(和你的ReadMessage超时保持一致) WaitHandle.WaitAll(_threadEvents.ToArray(), 10); // 批量处理所有结果 for (int i = 0; i < _threadResults.Count; i++) { UpdateClass(_threadResults[i]); } UpdateTextDisplay(); }
2. 更高效的方案:复用线程(避免每帧创建销毁线程)
每帧创建12个新线程会有不小的性能开销,改用线程池或者Task可以复用线程,进一步降低性能损耗。
用Task实现的示例
private List<Task<string>> _messageTasks = new List<Task<string>>(); void Update() { _messageTasks.Clear(); foreach (var port in _allPorts) { // 注意:这里要复制port变量,避免闭包陷阱 var portCopy = port; _messageTasks.Add(Task.Run(() => ReadMessage(portCopy))); } } void LateUpdate() { // 等待所有任务完成,最多10ms Task.WaitAll(_messageTasks.ToArray(), 10); // 处理结果,同时判断任务是否成功完成 foreach (var task in _messageTasks) { if (task.IsCompletedSuccessfully) { UpdateClass(task.Result); } else { Debug.LogWarning($"端口消息读取失败:{task.Exception?.Message}"); } } UpdateTextDisplay(); }
3. 关键优化:复用Socket连接
你的ReadMessage里每次都创建、连接、关闭SubscriberSocket,这是很大的性能浪费!建议提前初始化所有端口的Socket,复用它们:
private Dictionary<string, SubscriberSocket> _socketCache = new Dictionary<string, SubscriberSocket>(); void Start() { AsyncIO.ForceDotNet.Force(); foreach (var port in _allPorts) { string connectionString = PROTOCOL + CONTROLLER_IP + ":" + port; var subSocket = new SubscriberSocket(); subSocket.Connect(connectionString); subSocket.Subscribe(""); _socketCache.Add(port, subSocket); } } private string ReadMessage(string port) { string fullMessage = ""; someStruct parsedMessage; var timeout = new System.TimeSpan(0, 0, 0, 0, 10); if (_socketCache.TryGetValue(port, out var subSocket)) { subSocket.TryReceiveFrameString(timeout, out fullMessage); // 这里去掉Debug.Log,运行时日志会拖慢性能,调试时再开 // UnityEngine.Debug.Log("Message: " + fullMessage); } // 消息解析逻辑... return parsedMessage.someString; } void OnDestroy() { // 退出时关闭所有Socket foreach (var socket in _socketCache.Values) { socket.Close(); } _socketCache.Clear(); }
4. 终极优化:常驻后台线程
如果上面的方案还不够,你可以给每个端口分配一个常驻后台线程,持续接收消息,把结果放到线程安全的容器里,然后在LateUpdate里批量读取更新UI。这样完全避免了每帧创建线程的开销:
private ConcurrentDictionary<string, string> _latestMessages = new ConcurrentDictionary<string, string>(); private List<Thread> _permanentThreads = new List<Thread>(); private bool _isRunning = true; void Start() { AsyncIO.ForceDotNet.Force(); foreach (var port in _allPorts) { var portCopy = port; Thread thread = new Thread(() => { string connectionString = PROTOCOL + CONTROLLER_IP + ":" + portCopy; using (var subSocket = new SubscriberSocket()) { subSocket.Connect(connectionString); subSocket.Subscribe(""); var timeout = new System.TimeSpan(0, 0, 0, 0, 10); while (_isRunning) { string fullMessage = ""; subSocket.TryReceiveFrameString(timeout, out fullMessage); // 解析消息 someStruct parsedMessage = ParseMessage(fullMessage); _latestMessages[portCopy] = parsedMessage.someString; Thread.Sleep(1); // 避免占用过多CPU } } }); thread.IsBackground = true; thread.Start(); _permanentThreads.Add(thread); } } void LateUpdate() { foreach (var port in _allPorts) { if (_latestMessages.TryGetValue(port, out string message)) { UpdateClass(message); } } UpdateTextDisplay(); } void OnDestroy() { _isRunning = false; foreach (var thread in _permanentThreads) { thread.Join(); } }
按上面的步骤优化后,帧率应该能轻松回到60FPS以上。优先改等待方式和Socket复用,这两个是最见效的点。
内容的提问来源于stack exchange,提问作者Matt
相关产品推荐
相关产品推荐

