Unityでメインスレッドをフリーズしない方法


33

計算量の多いレベル生成アルゴリズムがあります。そのため、呼び出すと常にゲーム画面がフリーズします。ゲームがまだロード画面をレンダリングし続けている間に、ゲームがフリーズしていないことを示すために、関数を2番目のスレッドに配置するにはどうすればよいですか?


1
スレッドシステムを使用して、バックグラウンドで他のジョブを実行できます...しかし、それらはUnity APIで何も呼び出さない場合があります。それは、そのようなものはスレッドセーフではないからです。まだコメントしていないので、すぐにサンプルコードを自信を持って伝えることができません。
アルモ

Listメインスレッドで呼び出したい関数を保存するためのアクションの使用。ロックやコピーActionでリストをUpdate実行して元のリストをクリアし、一時リストに機能Actionという点で、コードをListメインスレッドで。これを行う方法については、他の投稿の UnityThreadを参照してください。たとえば、メインスレッドで関数を呼び出すには、 UnityThread.executeInUpdate(() => { transform.Rotate(new Vector3(0f, 90f, 0f)); });
プログラマー

回答:


48

更新: 2018年、Unityは作業をオフロードし、複数のCPUコアを使用する方法としてC#ジョブシステム展開しています。

以下の回答は、このシステムより前のものです。それでも動作しますが、ニーズに応じて、最新のUnityでより良いオプションが利用できる場合があります。特に、ジョブシステムは、以下で説明する、手動で作成されたスレッドが安全にアクセスできるものに関する制限の一部を解決するようです。たとえば、レイキャストを実行しメッシュを並行して構築するプレビューレポートを試す開発者

このジョブシステムを使用した経験のあるユーザーを招待して、エンジンの現在の状態を反映する独自の回答を追加します。


私は過去にUnityのヘビーウェイトタスク(通常は画像およびジオメトリ処理)にスレッドを使用しましたが、他のC#アプリケーションでスレッドを使用することと2つの注意点があります。

  1. Unityは多少古い.NETのサブセットを使用しているため、すぐに使用できない新しいスレッド機能とライブラリがいくつかありますが、基本はすべて揃っています。

  2. 上記のコメントでAlmoが指摘しているように、多くのUnityタイプはスレッドセーフではないため、メインスレッドから構築、使用、または比較しようとすると例外がスローされます。留意すべきこと:

    • 一般的なケースの1つは、GameObjectまたはMonobehaviour参照がメンバーにアクセスする前にnullであるかどうかを確認することです。myUnityObject == nullUnityEngine.Objectから派生したものに対してオーバーロードされた演算子を呼び出しますが、System.Object.ReferenceEquals()これをある程度回避します-Destroy ()ed GameObjectはオーバーロードを使用してnullと等しいが、まだReferenceEqualがnullではないことに注意してください。

    • Unityタイプからのパラメーターの読み取りは通常、別のスレッドで安全です(上記のようにnullをチェックするように注意している限り、すぐに例外をスローしません)が、メインスレッドが状態を変更しているかもしれないというPhilippの警告に注意あなたがそれを読んでいる間。一貫性のない状態を読み取らないようにするために、誰が何をいつ変更できるかについて規律を守る必要があります。意のままに再現しないでください。

    • ランダムおよび時間の静的メンバーは使用できません。ランダム性が必要な場合はスレッドごとにSystem.Randomのインスタンスを作成し、タイミング情報が必要な場合はSystem.Diagnostics.Stopwatchを作成します。

    • Mathf関数、Vector、Matrix、Quaternion、Color構造体はすべてスレッド間で正常に機能するため、ほとんどの計算を個別に実行できます。

    • GameObjectsの作成、Monobehavioursのアタッチ、またはテクスチャ、メッシュ、マテリアルなどの作成/更新はすべてメインスレッドで行う必要があります。過去にこれらを使用する必要があったときに、プロデューサー-コンシューマキューを設定しました。ここで、ワーカースレッドは生データ(メッシュまたはテクスチャに適用するための大きなベクトル/色の配列など)を準備します。メインスレッドの更新またはコルーチンがデータをポーリングして適用します。

これらのメモが邪魔にならないように、スレッド化された作業によく使用するパターンを以下に示します。私はそれがベストプラクティスのスタイルであることを保証しませんが、仕事は完了します。(改善のためのコメントや編集は大歓迎です-スレッド化は非常に深いトピックであり、基本的なことしか知りません

using UnityEngine;
using System.Threading; 

public class MyThreadedBehaviour : MonoBehaviour
{

    bool _threadRunning;
    Thread _thread;

    void Start()
    {
        // Begin our heavy work on a new thread.
        _thread = new Thread(ThreadedWork);
        _thread.Start();
    }


    void ThreadedWork()
    {
        _threadRunning = true;
        bool workDone = false;

        // This pattern lets us interrupt the work at a safe point if neeeded.
        while(_threadRunning && !workDone)
        {
            // Do Work...
        }
        _threadRunning = false;
    }

    void OnDisable()
    {
        // If the thread is still running, we should shut it down,
        // otherwise it can prevent the game from exiting correctly.
        if(_threadRunning)
        {
            // This forces the while loop in the ThreadedWork function to abort.
            _threadRunning = false;

            // This waits until the thread exits,
            // ensuring any cleanup we do after this is safe. 
            _thread.Join();
        }

        // Thread is guaranteed no longer running. Do other cleanup tasks.
    }
}

速度を上げるためにスレッド間で厳密に作業を分割する必要がなく、ゲームの残りの部分がカチカチ音をたて続けるようにブロックしないようにする方法を探している場合、Unityのより軽量なソリューションはCoroutinesです。これらは、いくつかの作業を実行し、エンジンに制御を戻し、実行を継続し、後でシームレスに再開できる機能です。

using UnityEngine;
using System.Collections;

public class MyYieldingBehaviour : MonoBehaviour
{ 

    void Start()
    {
        // Begin our heavy work in a coroutine.
        StartCoroutine(YieldingWork());
    }    

    IEnumerator YieldingWork()
    {
        bool workDone = false;

        while(!workDone)
        {
            // Let the engine run for a frame.
            yield return null;

            // Do Work...
        }
    }
}

エンジンは(私が知る限り)破壊されたオブジェクトからコルーチンを取り除くため、特別なクリーンアップの考慮事項は必要ありません。

メソッドのローカル状態はすべて、yieldおよび再開しても保持されます。そのため、多くの目的で、別のスレッドで中断せずに実行されているように見えます(ただし、メインスレッドで実行するのに便利です)。メインスレッドを不当に遅くしないように、各反復が十分に短いことを確認する必要があります。

重要な操作がyieldで分離されないようにすることで、シングルスレッドの動作の一貫性を得ることができます。メインスレッド上の他のスクリプトやシステムは、作業中のデータを変更できません。

yield return行にはいくつかのオプションがあります。あなたはできる...

  • yield return null 次のフレームのUpdate()後に再開する
  • yield return new WaitForFixedUpdate() 次のFixedUpdate()の後に再開する
  • yield return new WaitForSeconds(delay) 一定のゲーム時間が経過した後に再開する
  • yield return new WaitForEndOfFrame() GUIがレンダリングを終了した後に再開する
  • yield return myRequestどこmyRequestWWWのインスタンスは、要求されたデータは、ウェブまたはディスクからの読み込みが終了したら再開し、。
  • yield return otherCoroutinewhere otherCoroutineは、完了後に再開するCoroutineインスタンスotherCoroutineです。これは多くの場合、yield return StartCoroutine(OtherCoroutineMethod())実行時に新しいコルーチンにチェーンするためにフォームで使用されます。このコルーチンは、必要に応じてそれ自体を生成できます。

    • 実験的に、同じコンテキストで実行を連鎖させたい場合は、2番目StartCoroutineをスキップして単純に記述yield return OtherCoroutineMethod()するだけで同じ目標を達成できます。

      内部のラッピングは、StartCoroutineあなたが第二の目的に関連して、ネストされたコルーチンを実行したい場合は、まだのように、有用である可能性がありますyield return otherObject.StartCoroutine(OtherObjectsCoroutineMethod())

...コルーチンをいつ次のターンに入れるかによって異なります。

またはyield break;、コルーチンが最後に到達する前に停止return;するには、従来の方法を早期に使用する方法があります。


反復に20ミリ秒以上かかった後にのみコルーチンを使用して生成する方法はありますか?
DarkDestry

@DarkDestryストップウォッチインスタンスを使用して、内側のループで費やしている時間を計り、ストップウォッチをリセットして内側のループを再開する前に、外側のループに分岐します。
DMGregory

1
いい答えだ!コルーチンを使用するもう1つの理由は、webgl用にビルドする必要がある場合、スレッドを使用する場合は機能しないということです。今、巨大なプロジェクトでその頭痛に座っ:P
ミカエルHögström

良い答えと感謝。ちょうど私のエラーを取得私は複数回停止し、スレッドを再起動する必要がある場合、私は中止しようとすると、開始、不思議
flankechen

@flankechenスレッドの起動と破棄は比較的高価な操作なので、多くの場合、スレッドを使用可能にするが休眠状態にしたほうがよいでしょう。ユースケースを詳細に説明する新しい質問を投稿したい場合、人々はそれを達成するための効率的な方法を提案できますか?
DMGregory

0

重い計算を別のスレッドに入れることはできますが、UnityのAPIはスレッドセーフではないため、メインスレッドで実行する必要があります。

Asset Storeでこのパッケージを試してみると、スレッドの使用が簡単になります。http://u3d.as/wQg 1行のコードのみを使用して、スレッドを開始し、Unity APIを安全に実行できます。



0

@DMGregoryはそれを本当にうまく説明しました。

スレッドとコルーチンの両方を使用できます。以前はメインスレッドをオフロードし、後でメインのスレッドに制御を返しました。重いリフトを別のスレッドにプッシュする方が合理的です。したがって、ジョブキューはあなたが求めているものかもしれません。

Unity Wikiには、本当に良いJobQueueサンプルスクリプトがあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.