Unityで時間経過を必要とする処理をプログラムしたい

追記(2023/3/20)

このページのやり方でタイマーなどを実装するのは非推奨です。UnityはUnityアプリケーションが選択されていないとき、止まります。(これは仕様。)しかし別スレッドで実行しているものは止まらないので、同期ズレが起こり予想外の動作をするためです。

やりたいこと

あるアクション

3秒くらい待つ

何かを実行

愚直に実装してみる (動かない..)

書いたけど動かないコード

void SampleMethod()
{
  Action(); // あるアクションを実行
  Thread.Sleep(3000); //3秒待つ
  Foo(); // 何かを実行
}

ただこれを実行すると止まる。3秒間フリーズする理由

フリーズする原因

Thread.Sleep()はそもそもスレッドを止めるメソッド。UnityEngineが実行されているスレッド自体を止めてしまっているのでゲーム自体がフリーズする。

補足: そもそもスレッドとは

スレッドとは、ざっくりいうとCPUの割り当て実行単位のこと。基本的にプロセスというプログラムの実行単位には複数のスレッドを持つ。そして複数のスレッドは同時に並列動作させることができる。
(要約元: https://atmarkit.itmedia.co.jp/ait/articles/1410/30/news150.html)

マルチスレッドで実装してみる

書いたけど動かないコード

void SampleMethod()
{
  Action(); // あるアクションを実行
  new Thread(() => {
    Thread.Sleep(3000); //3秒待つ
    Foo(); // 何かを実行
  }).Start();
}

このコードだと場合によっては動く。しかしUnityの機能を用いる場合は動かない。なぜならメインスレッドで実行する必要があるから。

問題点

Foo()をメインスレッド以外で実行している。

... can only be called from the main thread.

↑こんなエラーが出る。

どうする

UniTaskとかであれば、メインスレッドに実行させるメソッドがあるみたい。だけど今回は外部のモジュールを使わずに書いてみる。そのときに使うのがSynchronizationContext

SynchronizationContextを使い実装する

void SampleMethod()
{
  Action(); // あるアクションを実行
  var context = SynchronizationContext.Current;
  new Thread(() => {
    Thread.Sleep(3000); //3秒待つ
    context.Post(_ => {
    Foo(); // 何かを実行
    }, null);
  }).Start();
}

これでメインスレッドでFooを動かすことができる。

SynchronizationContextって何

公式ドキュメントによるとフリースレッドコンテキストを提供する基本クラスらしい。このページによるとスレッド識別子からその他リソースとか色々含まれている構造体らしい。

このコンテキストを使いPostメソッドでコールバック関数を登録すると、指定したスレッドコンテキストでコールバック関数を実行してくれる。ちなみにPostメソッドではなくSendメソッドも存在する。

Send → 同期メッセージを同期コンテキストに送信する
Post → 非同期メッセージを同期コンテキストに送信する

普段Postしか使ってないのでSendが使えるか分からない。使えなかったらNotSupportedExceptionが出るらしい。

コメント

タイトルとURLをコピーしました