'Is OnDestroy reliable in Unity?

In Unity I have a script in which I connect to a socket via TCP and want to use this connection every frame. I need to dispose and clean up after that. My idea was to use Start() to start a connection and OnDestroy()

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

I need the cleanup to execute whatever happens. Does the OnDestroy() method guarantee to be called before the application stops (in standalone mode and in editor) no matter what happens to the object? If not, how can I guarantee the cleanup?



Solution 1:[1]

No it is not!

Even OnApplicationQuit might not get called when your app e.g. crashes for some reason.

And there are other specific cases where neither is called. I know that from my own experience that e.g. on the HoloLens2 apps are not closed but only hibernated. If you then close them via the HoloLens home "menu" then you actually kill them via task manager.

This is pretty dirty and causes neither OnDestroy nor OnApplicationQuit or any other Unity specific messages to be called and we ended up with zomby threads and still occupied TCP ports.


If you really want to go sure (e.g. for giving free the connection, killing threads etc) what I finally did was creating a dedicated class with deconstructor (Finalizer)

The deconstructor is pure c# and does not rely on Unity being shutdown correctly so it is guaranteed to be called even if the app was terminated due to a crash as soon as the Garbage Collector automatically does its job.

A MonoBehaviour itself shouldn't implement any constructor nor destructor but a private "normal" class can:

public class Foo : MonoBehaviour
{
    private class FooInternal
    {
        private bool disposed; 

        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()
        {
            Dispose();
        }

        public void Dispose()
        {
            if(disposed) return;

            disposed = true; 

            // terminate thread, connection etc
        }
    }

    private FooInternal _internal;

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

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

    void OnDestroy ()
    {
        _internal.Dispose();
    }
}

if you never pass on the reference to _internal to anything else, the GC should automatically kill it after this instance has been destroyed as well.

Solution 2:[2]

You're looking for OnEnable for establishing your connection, and OnDisable for cleaning it up.

The issue with OnDestroy (and OnApplicationQuit fwiw) is it won't be called if the script is disabled, which can happen if:

  • You call SetActive(false) on the GameObject or any of its parents.
  • You set enabled = false on the script component.
  • You disable the GameObject or any of its parents in play mode by unchecking the box in the inspector.
  • You disable the script component in play mode by unchecking the box in the inspector.
  • The editor is about to recompile the scripts while play mode is running (an especially irritating event to miss for certain types of cleanup code [cough lookin' at you, Vuforia]).

The issue with Start is it won't be called if the script is re-enabled after having been disabled (it'll be called the first time, but no more), which happens in all of the cases opposite of the above list (as well as when the editor finishes recompilation of scripts in play mode).

On the other hand:

  • OnDisable will be called when the script transitions to disabled for any of the above reasons, plus all the stuff that OnDestroy and OnApplicationQuit cover.
  • OnEnable will be called when the script transitions to enabled for any of the above reasons, plus all the stuff that Start covers.

Additionally, you will have a 1:1 correspondence of OnEnable and OnDisable calls by Unity (of course you can call them yourself whenever you want).

You will only almost have a 1:1 correspondence of Start and OnDestroy: The notable exception is if a script component is initially disabled (i.e. you disabled it in edit mode) but its GameObject is enabled, you'll still get OnDestroy called on the script even if Start wasn't called (which may be a bug now that I think about it). Plus, of course, you will miss all the above cases.

OnEnable + OnDisable, however, handle all that cleanly, and generally do exactly what you hope that they do, in a straightforward manner with (theoretically) no weird quirks or gotchas.


For the question itself, yes, OnDestroy is reliable: It is called as documented when the script is destroyed.

Of course it wouldn't be called if the application crashes, but in general you'd have bigger problems and that's not the kind of thing you normally code around. It also probably wouldn't be called if you exploded a grenade next to the machine running the game but, again, that's all in "undefined behavior" territory.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Jason C