Skip to content

Instantly share code, notes, and snippets.

@STARasGAMES
Last active February 4, 2026 14:16
Show Gist options
  • Select an option

  • Save STARasGAMES/43402bc52bf49e46ce3cc5fbe3182663 to your computer and use it in GitHub Desktop.

Select an option

Save STARasGAMES/43402bc52bf49e46ce3cc5fbe3182663 to your computer and use it in GitHub Desktop.
Detailed description of how Object.Destroy() and Object.DestroyImmediate() behaves in Unity 6.3 with destroyCancellationToken. There is a script you can use in your environment to test the behavior.
using System.Threading;
using UnityEngine;
public class DestroyCancellationTokeTest : MonoBehaviour
{
private CancellationToken _token;
private void Awake()
{
Log($"Awake(), default state: {_token.CanBeCanceled} {_token.IsCancellationRequested}");
}
private void OnEnable()
{
_token = destroyCancellationToken;
Log($"OnEnable(), created state: {_token.CanBeCanceled} {_token.IsCancellationRequested}");
Log($"OnEnable(), are equal: {_token == destroyCancellationToken}");
_token.Register(() => { this.Log($"Registered synchron callback: {this._token.CanBeCanceled} {this._token.IsCancellationRequested}"); }, true);
_token.Register(() => { this.Log($"Registered not synchron callback: {this._token.CanBeCanceled} {this._token.IsCancellationRequested}"); }, false);
_token.Register(() => { this.Log($"Registered not synchron2 callback: {this._token.CanBeCanceled} {this._token.IsCancellationRequested}"); }, false);
_token.Register(() => { this.Log($"Registered synchron2 callback: {this._token.CanBeCanceled} {this._token.IsCancellationRequested}"); }, true);
}
private void Start()
{
Log($"Start(), before destroy call: {_token.CanBeCanceled} {_token.IsCancellationRequested}");
// # Behavior of Object.Destroy() in Unity 6.3:
// # 1. this.OnDisable() called immediately, before next line.
// # 2. Next line after Destroy() is executed with `this != null`.
// # All next steps are delayed.
// # 3. Internally `_token.IsCancellationRequested` is set to `true`.
// # 4. Calls token's registered callbacks in **REVERSE** order.
// # 5. Calls this.OnDestroy().
// # NOTE: Unity's null check `this == null` **NEVER** returns true in this chain.
// # NOTE: After Destroy() is called no other callbacks (Update, LateUpdate) will be invoked despite delayed execution of steps 3,4,5.
Destroy(this);
// # Behavior of Object.DestroyImmediate() in Unity 6.3:
// # 1. this.OnDisable() called immediately, before next line. `this != null` and `isCancelled == false`.
// # 2. Internally `_token.IsCancellationRequested` is set to `true`.
// # 3. Calls registered callbacks in **REVERSE** order. `this != null` and `isCancelled == true`.
// # 4. Calls this.OnDestroy() immediately, before next line. `this != null` and `isCancelled == true`.
// # 5. Next line after DestroyImmediate() is executed, finally `this == null`.
// DestroyImmediate(this);
Log($"Start(), after destroy call: {_token.CanBeCanceled} {_token.IsCancellationRequested}");
}
private void Update()
{
Log($"Update() {_token.CanBeCanceled} {_token.IsCancellationRequested}");
}
private void LateUpdate()
{
Log($"LateUpdate() {_token.CanBeCanceled} {_token.IsCancellationRequested}");
}
private void OnDisable()
{
Log($"OnDisable() {_token.CanBeCanceled} {_token.IsCancellationRequested}");
}
private void OnDestroy()
{
Log($"OnDestroy() {_token.CanBeCanceled} {_token.IsCancellationRequested}");
}
private void Log(string message)
{
Debug.Log($"[DestroyCancellationTokeTest] {message}, {(this == null ? "THIS IS NULL" : "")}");
}
}

Behavior of Object.Destroy() in Unity 6.3:

  1. this.OnDisable() called immediately, before next line.
  2. Next line after Destroy() is executed with this != null. All next steps are delayed.
  3. Internally _token.IsCancellationRequested is set to true.
  4. Calls token's registered callbacks in REVERSE order.
  5. Calls this.OnDestroy(). NOTE: Unity's null check this == null NEVER returns true in this chain. NOTE: After Destroy() is called no other callbacks (Update, LateUpdate) will be invoked despite delayed execution of steps 3,4,5.

Behavior of Object.DestroyImmediate() in Unity 6.3:

  1. this.OnDisable() called immediately, before next line. this != null and isCancelled == false.
  2. Internally _token.IsCancellationRequested is set to true.
  3. Calls registered callbacks in REVERSE order. this != null and isCancelled == true.
  4. Calls this.OnDestroy() immediately, before next line. this != null and isCancelled == true.
  5. Next line after DestroyImmediate() is executed, finally this == null.

Unity Object Destruction Semantics (Unity 6.3) summary by ChatGPT

Scope: This document describes observable behavior of Object.Destroy and Object.DestroyImmediate in Unity 6.3.
Any implementation details are explicitly marked and must not be relied upon as API guarantees.

Important: All null checks mentioned here refer to Unity’s overloaded
UnityEngine.Object == null, not CLR reference null.


Object.Destroy()

Summary

Disable immediately, destroy later (end of frame).

Immediate effects (during the call)

  • OnDisable() is invoked immediately, before Destroy() returns.
  • After the call returns, the object is still considered alive:
    • this != null evaluates to true.
    • Script execution continues normally.

Deferred effects (destruction finalization)

  • The object is marked for destruction and finalized later in the same frame.
  • During finalization:
    • OnDestroy() is invoked.
    • After this point, this == null evaluates to true.

Execution guarantees

  • After Destroy() is called:
    • No further lifecycle callbacks (Update, LateUpdate, etc.) will be invoked.
    • The object should be treated as logically dead, even though destruction is deferred.

Implementation details (non-contractual)

  • Internally, Unity cancels the object’s lifetime token and invokes its registered callbacks.
  • Callback invocation order has been observed to be reverse registration order.
  • These details are not part of Unity’s public API and may change.

Object.DestroyImmediate()

Summary

Destroy immediately and synchronously.

Immediate effects (during the call)

  • OnDisable() is invoked immediately.
  • The object is destroyed synchronously.
  • OnDestroy() is invoked before DestroyImmediate() returns.

After the call returns

  • The object is fully destroyed.
  • this == null evaluates to true.
  • Any further access to the object must be treated as invalid.

Implementation details (non-contractual)

  • Lifetime cancellation and registered callbacks are triggered during the call.
  • Observed behavior places these callbacks before OnDestroy().
  • This ordering is not guaranteed by the API.

Practical Guidelines

  • Prefer Destroy() in gameplay code.
  • Use DestroyImmediate() only when explicitly required (e.g. editor tooling).
  • Never rely on:
    • Internal cancellation tokens
    • Callback ordering
    • Exact timing beyond what is documented above
  • After calling Destroy():
    • Do not expect further updates
    • Do not schedule work assuming the object will survive the frame

Rule of Thumb

  • Destroy()disable now, destroy later
  • DestroyImmediate()destroy now
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment