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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:34:24