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

如何在C# Unity中正确释放NetMQ的Socket、Monitor与Poller?

NetMQ在Unity中的资源释放与重连问题解决

问题背景

在Unity环境中使用NetMQ实现Pub/Sub模式,搭配NetMQPoller和NetMQMonitor监控连接状态。初始化代码如下:

m_Publisher = new PublisherSocket();
m_Subscriber = new SubscriberSocket();
m_Poller = new NetMQPoller();
m_SubMonitor = new NetMQMonitor(m_Subscriber, m_InprocSubMonitorAddress, SocketEvents.Connected | SocketEvents.Disconnected);

// Events
m_SubMonitor.Connected += (s, e) =>
{
    Connected = true;
    UnityMainThreadDispatcher.Instance().Enqueue(() => OnConnected?.Invoke(this, EventArgs.Empty));
};

m_SubMonitor.Disconnected += (s, e) =>
{
    Connected = false;
    UnityMainThreadDispatcher.Instance().Enqueue(() => OnDisconnected?.Invoke(this, EventArgs.Empty));
};

// Monitoring
m_SubMonitor.AttachToPoller(m_Poller);
m_Poller.RunAsync();

// Subscriber
m_Subscriber.Connect($"tcp://{m_IPAddress}:{m_Port}");

// Publisher
m_Publisher.Bind($"tcp://*:{m_Port}");

当前释放逻辑如下:

m_SubMonitor.DetachFromPoller();
m_Poller?.Stop();
m_Poller?.Dispose();
m_SubMonitor?.Stop();
m_SubMonitor?.Dispose();

m_Publisher?.Unbind($"tcp://*:{m_Port}");
//m_Publisher?.Close();
m_Publisher?.Dispose();

m_Subscriber?.Disconnect($"tcp://{m_IPAddress}:{m_Port}");
//m_Subscriber?.Close();
m_Subscriber?.Dispose();

问题:尝试连接离线地址后取消连接,重试时会触发**“地址已被占用”**错误,指向inproc://sub.inproc地址及NetMQMonitor创建环节,推测是资源释放顺序或方式错误。同时疑问:每次断开是否需要调用NetMQConfig.Cleanup?目前仅在应用退出时调用一次并传入false避免阻塞是否正确?


正确的资源释放流程

NetMQ的资源释放有严格顺序要求,错误顺序会导致inproc地址残留、线程资源未释放等问题,正确步骤如下:

  1. 停止并清理Monitor
    先从Poller中分离Monitor,再同步停止Monitor确保内部线程终止,避免资源泄漏:

    if (m_SubMonitor != null)
    {
        m_SubMonitor.DetachFromPoller();
        m_SubMonitor.Stop();
        m_SubMonitor.Dispose();
        m_SubMonitor = null;
    }
    
  2. 停止并清理Poller
    停止Poller后需等待异步线程退出,避免后续资源访问异常:

    if (m_Poller != null)
    {
        m_Poller.Stop();
        m_Poller.WaitForExit();
        m_Poller.Dispose();
        m_Poller = null;
    }
    
  3. 清理Socket资源
    必须调用Close()处理Socket内部队列和连接,仅调用Dispose()清理不彻底:

    // 处理Publisher
    if (m_Publisher != null)
    {
        try
        {
            m_Publisher.Unbind($"tcp://*:{m_Port}");
        }
        catch (NetMQException)
        {
            // 捕获解绑失败异常(如已提前解绑)
        }
        m_Publisher.Close();
        m_Publisher.Dispose();
        m_Publisher = null;
    }
    
    // 处理Subscriber
    if (m_Subscriber != null)
    {
        try
        {
            m_Subscriber.Disconnect($"tcp://{m_IPAddress}:{m_Port}");
        }
        catch (NetMQException)
        {
            // 捕获断开失败异常(如已提前断开)
        }
        m_Subscriber.Close();
        m_Subscriber.Dispose();
        m_Subscriber = null;
    }
    
  4. 线程安全处理
    由于资源在多线程中使用,释放操作需加锁避免并发访问:

    private readonly object m_Lock = new object();
    
    public void CleanupResources()
    {
        lock (m_Lock)
        {
            // 上述释放步骤放在此处
        }
    }
    

关于NetMQConfig.Cleanup的使用

  • 不需要每次断开都调用:NetMQConfig.Cleanup()用于清理NetMQ全局资源(如IO线程、上下文),频繁调用会影响性能,还可能导致后续Socket创建异常。
  • 应用退出时调用是正确的:仅在Unity应用退出时调用一次,传入false避免主线程阻塞,需确保所有Socket、Poller、Monitor已完成释放后再执行:
    private void OnApplicationQuit()
    {
        CleanupResources();
        NetMQConfig.Cleanup(false);
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 18:05:24