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

Unity中订阅事件的追踪、性能分析与内存泄漏检测问题

问题与代码
private void Start()
{
    _winPanelController.OnGiveAwards += CalculateCoins;
    _revivePaymentManager.OnUpdatedBalance += SaveBalanceToJSON;
    _revivePaymentManager.OnPayingFinished += RespawnAllEnemies;
    _revivePaymentManager.OnPayingFinished += RevivePlayer;
    _revivePaymentManager.OnPayingNotFinished += InformPlayerNotEnoughGems;
    _actorUI.OnPlayerStop += SetPlayerMovement;
    _wavesController.OnWin += ActivationWinPanel;
    _wavesController.OnStoppedWave += StopSpawningForAllSpawners;
    _wavesController.OnEnemyLevelUp += SetNewLevelForEnemies;
    _player.OnDead += EnableRevivePanel;
    _player.OnRevived += RespawnAllEnemies;

    foreach (var spawner in _enemySpawner)
    {
        spawner.OnXPCollected += CalculateCollectedXP;
        spawner.OnDamageCollected += CalculateCollectedDamage;
    }
}

private void OnDestroy()
{
    _winPanelController.OnGiveAwards -= CalculateCoins;
    _revivePaymentManager.OnUpdatedBalance -= SaveBalanceToJSON;
    _revivePaymentManager.OnPayingFinished -= RespawnAllEnemies;
    _revivePaymentManager.OnPayingFinished -= RevivePlayer;
    _revivePaymentManager.OnPayingNotFinished -= InformPlayerNotEnoughGems;
    _wavesController.OnWin -= ActivationWinPanel;
    _wavesController.OnStoppedWave -= StopSpawningForAllSpawners;
    _wavesController.OnEnemyLevelUp -= SetNewLevelForEnemies;
    _player.OnDead -= EnableRevivePanel;
    _player.OnRevived -= RespawnAllEnemies;

    foreach (var spawner in _enemySpawner)
    {
        spawner.OnXPCollected -= CalculateCollectedXP;
        spawner.OnDamageCollected -= CalculateCollectedDamage;
    }
}

我的疑问是:我该如何追踪已订阅事件的数量、它们占用的RAM或CPU时长?或者是否无法进行此类追踪?如果我不取消这些订阅会发生什么,该如何检测由此产生的内存泄漏?
我曾尝试在Memory Profiler中查找与事件相关的内容,但一无所获。我在Profiler中找到的最相关内容是进程Update.ScriptsRunBehaviorUpdate > BehaviorUpdate > EventSystem.Update() [Invoke]。这是Unity的标准EventSystem吗?


解答

一、事件订阅的追踪方法

1. 追踪订阅数量

  • 针对自定义事件,可自行封装事件容器类,在+=和-=操作时维护订阅计数。比如用字典记录每个事件的订阅者数量,或给事件添加SubscriberCount属性实时更新。
  • 若使用Unity自带的UnityEvent,可通过反射访问其内部的m_Calls列表获取订阅数,但反射有性能开销,仅适合调试场景。

2. RAM与CPU开销追踪

  • RAM占用:单个事件订阅本质是委托引用,内存开销仅为指针大小,几乎可忽略。要追踪的话,在Unity Memory Profiler的Managed Objects视图中筛选Delegate类型,查看相关实例的数量与内存占比,仅当订阅量极大时才会有明显影响。
  • CPU时长:在Unity Profiler的CPU Usage视图中,直接查看事件回调函数(如CalculateCoins、SaveBalanceToJSON)的耗时。若要统计事件触发的总耗时,可添加自定义Profiler标记:
    // 在事件触发处添加
    Profiler.BeginSample("OnGiveAwards Trigger");
    OnGiveAwards?.Invoke();
    Profiler.EndSample();
    
    即可在Profiler中看到该事件触发的总耗时。

二、不取消订阅的后果

  • 内存泄漏:若订阅者所在GameObject被销毁,但事件发布者(如_winPanelController)仍存活,委托会持有订阅者对象的引用,导致GC无法回收该对象,引发内存泄漏。
  • 无效回调执行:发布者后续触发事件时,已销毁订阅者的回调仍会被执行,大概率引发空引用异常或逻辑错误(如访问已销毁对象的组件)。

三、内存泄漏的检测方法

  1. Unity Memory Profiler:
    • 场景初始化时拍摄内存快照,销毁订阅者对象后触发GC.Collect(),再拍摄第二个快照。对比两个快照,查找本该被销毁却残留的脚本实例。
    • 查看残留对象的引用链:在Memory Profiler中选中残留实例,查看其引用来源,若指向某事件委托,即可确认是未取消订阅导致的泄漏。
  2. 自定义检测逻辑:
    • 在脚本中用列表记录所有订阅的委托,OnDestroy时逐一校验是否已移除;或在调试模式下,打印未取消的订阅信息。

四、关于Profiler中的EventSystem

你看到的Update.ScriptsRunBehaviorUpdate > BehaviorUpdate > EventSystem.Update() [Invoke]是Unity标准的UI EventSystem,负责处理按钮点击、拖拽等UI交互事件,和你代码中的自定义事件系统无关。你的自定义事件触发不会出现在这个调用栈中,而是会直接显示在对应回调函数的调用栈里。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 16:52:39