🐷

インスタンスの生存期間を調べる【Unity/C#】

2022/12/18に公開約3,400字

目的

  • Unityで、生成されたインスタンスがいつまで生きているのかを、生成方法によって比較したい。
  • Scene遷移したときの破棄されるのか、を調べる。
  • [公開後追記]今回の実験のタイマーのソースコードが、「非同期メソッドに対してのキャンセル処理を書いていないこと」や「コールバックの登録削除をしていないこと」などで、しっかり実験できていないと思いました。)

環境

  • Unity 2021.3.7f1

実験

比較するパターン

  • パターンA: GameObjectにアタッチされているスクリプト(MonoBehaviour継承クラス)のインスタンスでタイマーを実行
  • パターンB: Pure C#クラス(MonoBehaviour継承クラスからインスタンス化する)でタイマーを実行

実験条件

  • 1つ目のSceneでインスタンスを生成し、一定秒数後(8秒)後に2つ目のSceneに遷移する
  • 生成されたインスタンスでは、1秒起きにDebug.Logを出力する。いつまで出力されるか検証。

パターンA

  • 以下のスクリプトをGameObjectにアタッチ
CountDownMonoBehaviour.cs
public class CountDownMonoBehaviour : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(CountDown());
    }
    
    IEnumerator CountDown()
    {
        var time = 30;
        while (time > 0)
        {
            time--;
            Debug.Log("CountDownMonoBehaviour: 30回繰り返します");
            yield return new WaitForSeconds(1);
        }
    }
}

パターンB

  • CoundDownPureCSharpクラスのインスタンスを、Scene内にアタッチされたManagerから呼び出す。
CoundDownPureCSharp.cs
public class CountDownPureCSharp
{
    public CountDownPureCSharp()
    {
        CountDownByTask();
    }
    
    private async void CountDownByTask()
    {
        var time = 30;
        while (time > 0)
        {
            time--;
            Debug.Log("CountDownPureC#: 30回繰り返します");
            await Task.Delay(1000);
        }
    }
}
  • こちらがインスタンス化を担当するManager
SceneManager.cs
public class SceneManager : MonoBehaviour
{
    void Start()
    {
        Debug.Log("====1つ目のScene====");
        Invoke(nameof(次のSceneにいくメソッド), 8f);
        new CountDownPureCSharp();
    }
}

結果

パターン 1つ目のScene 2つ目のScene 再生終了後
パターンA(MonoBehaviour) カウントする カウントしない カウントしない
パターンB(Pure C#) カウントする カウントする カウントする
  • 再生終了後のログ(一部Collapseが上手くいっていないが)
  • MonoBehaviourを継承したインスタンスはScene遷移とともに破棄された
  • Pure C#の方はScene遷移しても、再生を終了しても破棄されない。(インスタンス化を担当したSceneManagerが消えても残り続ける) => 扱いが要注意!
  • [公開後追記]Taskの処理の方でcancellation tokenを呼んでいないことが非同期メソッドが続いてしまう原因?
    • 親切な方からコメント

追加実験

実験条件

  • タイマーのカウントダウン方法を、TaskやCoroutineではなく、Timerクラスのインスタンスを生成することによるメソッドに変更してみた(パターンA, パターンBともに)
using System;
using System.Timer;

... 省略 CountDownByTimerClassメソッドを呼び出す...
    private void CountDownByTimerClass()
    {
        int num = 0;
 
        // タイマーの間隔(ミリ秒)
        Timer timer = new Timer(1000);
 
        // タイマーの処理
        timer.Elapsed += (sender, e) =>
        {
            if (num < 30)
            {
                Debug.Log("(Timerクラス経由)CountDownPureC#またはMonoBehaviour: 30回繰り返します");
                num++;
            }
            else
            {
                timer.Stop();
                Debug.Log("処理を終了しました");
            }
        };
 
        // タイマーを開始する
        timer.Start();
    }

結果

  • パターンA、Bともにシーン遷移後、再生終了後までカウントされ続ける
パターン 1つ目のScene 2つ目のScene 再生終了後
パターンA(MonoBehaviour) カウントする カウントする カウントする
パターンB(Pure C#) カウントする カウントする カウントする
  • 再生終了後のログ
  • これは、パターンA(MonoBehaviour)であっても、そのメソッドの中で結局Pure C#をインスタンス化しているため(new Timer)、パターンBと変わらない
  • [公開後追記] timer.Elapsed += これに対して -= を呼んでいないことがカウントし続ける原因?

その他

  • コンパイルが一度走ると、再生終了後にもカウントされ続けていた動きは止まる。
  • Dispose周り今後調べる

Discussion

ログインするとコメントできます