【问题标题】:Is OnDestroy reliable in Unity?OnDestroy 在 Unity 中是否可靠?
【发布时间】:2020-12-09 19:00:06
【问题描述】:

在 Unity 中,我有一个脚本,在该脚本中我通过 TCP 连接到一个套接字,并希望每帧都使用这个连接。在那之后我需要处理和清理。我的想法是使用Start() 启动连接并使用OnDestroy()

public class Foo : MonoBehaviour
{
    void Start()
    {
        // start TCP connection
    }
    void Update()
    {
        // use connection
    }
    void OnDestroy()
    {
        // cleanup
    }
}

我需要清理来执行任何发生的事情。无论对象发生什么情况,OnDestroy() 方法是否保证在应用程序停止之前(在独立模式和编辑器中)被调用?如果没有,我如何保证清理?

【问题讨论】:

  • 第一句话,很抱歉说的很明显,但总不能保证一定会发生。拔掉电源线主机突然关机怎么办? (如果您想知道是否可能冒着在服务器上的数据库中出现临时无效状态的风险,例如,等待 ondestroy 调用某些东西以恢复良好状态)
  • 然后,查看文档:docs.unity3d.com/ScriptReference/MonoBehaviour.OnDestroy.html,似乎OnDestroy 在场景结束时被调用,并且对象是活动。所以非活动对象不会执行它。如果用户_正常_退出应用程序_它应该被调用。但我假设如果发生突然崩溃,或者用户强制终止进程,它不会被调用。
  • @ThomasWeller 这就是为什么我不想每帧都建立和处理连接,只有一次
  • @ThomasWeller 不能通过简单的异步方式发送请求来使用连接,因此不需要等待特定帧更新中的答案?但除此之外,是的,在线游戏通常使用更松散的协议,例如 UDP 而不是 TCP,正是因为它具有更多的反应性,它不需要 ACK 消息。
  • @Pac0 我明白了。那么,您对确保进行此清理有何建议?

标签: c# unity3d


【解决方案1】:

不,不是!

即使是 OnApplicationQuit 也可能不会在您的应用程序(例如由于某种原因崩溃。

还有其他一些特定情况都没有调用。我从我自己的经验中知道,例如HoloLens2 上的应用程序没有关闭,只是处于休眠状态。如果您随后通过 HoloLens 主“菜单”关闭它们,那么您实际上是通过任务管理器杀死它们。

这很脏,不会导致 OnDestroyOnApplicationQuit 或任何其他 Unity 特定的消息被调用,我们最终得到了僵尸线程并且仍然占用了 TCP 端口。


如果你真的想确定(例如,释放连接、杀死线程等),我最终所做的就是创建一个带有解构函数的专用类 (Finalizer)

解构器是纯 c# 并且不依赖于正确关闭 Unity,因此即使应用程序因崩溃而终止,只要垃圾收集器自动完成其工作,它也可以保证被调用。

public class Foo : MonoBehaviour
{
    private class FooInternal
    {
        public FooInternal()
        {
            // create TCP connection
            // start thread etc
        }

        public void Update ()
        {
            // e.g. forward the Update call in order to handle received messages
            // in the Unity main thread
        }

        public ~FooInternal()
        {
            // terminate thread, connection etc
        }
    }

    private FooInternal _internal;

    void Start()
    {
        _internal = new FooInternal ();
    }

    void Update()
    {
        _internal.Update();
    }
}

如果你从未将_internal 的引用传递给其他任何东西,GC 应该在这个实例也被销毁后自动终止它。

【讨论】:

    【解决方案2】:

    您正在寻找 OnEnable 来建立连接,并寻找 OnDisable 来清理它。

    OnDestroy(和OnApplicationQuit fwiw)的问题是如果脚本被禁用,它将不会被调用,如果发生这种情况:

    • 您在GameObject 或其任何父母上致电SetActive(false)
    • 您在脚本组件上设置了enabled = false
    • 您可以通过取消选中检查器中的框来禁用 GameObject 或其任何父级在播放模式下。
    • 您可以通过取消选中检查器中的框来禁用播放模式下的脚本组件。
    • 编辑器即将在播放模式运行时重新编译脚本(对于某些类型的清理代码[咳嗽 lookin' at you, Vuforia],这是一个特别令人讨厌的事件)。

    Start 的问题是,如果脚本在被禁用后重新启用,它将不会被调用(它会第一次被调用,但不会再被调用),这在与上述列表相反的所有情况下都会发生(以及当编辑器完成在播放模式下重新编译脚本时)。

    另一方面:

    • OnDisable 将在脚本由于上述任何原因转换为禁用时被调用,加上OnDestroyOnApplicationQuit 涵盖的所有内容。
    • OnEnable 将在脚本因上述任何原因转换为启用时调用,加上Start 涵盖的所有内容。

    此外,您通过 Unity 以 1:1 的比例对应 OnEnableOnDisable 调用(当然,您可以随时自己调用)。

    您将几乎拥有StartOnDestroy 的 1:1 对应关系:值得注意的例外是如果脚本组件最初被禁用(即您在编辑模式下禁用它)但它的GameObject 已启用,即使没有调用Start,您仍然会在脚本上调用OnDestroy(现在我想这可能是一个错误)。另外,当然,您会错过上述所有情况。

    OnEnable + OnDisable 但是,要干净利落地处理所有这些事情,并且通常会按照您希望他们做的事情以一种直接的方式(理论上)没有奇怪的怪癖或陷阱。


    对于问题本身,是的,OnDestroy 是可靠的:当脚本被销毁时,它被称为记录。

    当然,如果应用程序崩溃,它不会被调用,但通常你会遇到更大的问题,这不是你通常编码的那种事情。如果你在运行游戏的机器旁边引爆了一枚手榴弹,它也可能不会被调用,但同样,这一切都在“未定义行为”领域。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多