Unityで、起動時にフリーズした様な動作となるのが嫌で、非同期処理とマルチスレッドとを調べました。
Unity API を使う部分は、メインスレッドに入れなければならないことに注意し、非同期処理・マルチスレッドを実施すればよいことが分かりました。
詳しくは、以下の二つのサイトに書かれています。(じゃあ、この記事は何なんだよ!)
ということで、忘備録ですw
最初に困ったコード
async void Start()
{
Debug.Log("Start: " + Thread.CurrentThread.ManagedThreadId);
Debug.Log("Heavy Method: " + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(5000);
Debug.Log("Unity API Method: " + Thread.CurrentThread.ManagedThreadId);
transform.position = new Vector3(0, 0, 0);
Debug.Log("Start end: " + Thread.CurrentThread.ManagedThreadId);
}
Thread.Sleep(5000)
の行でフリーズした様になります。
メインスレッドを阻害しているということです。
Unity API コード部分があかんやつ
Task.Run
を使って、全部マルチスレッドで処理しようとすると
async void Start()
{
Debug.Log("Start: " + Thread.CurrentThread.ManagedThreadId);
await Task.Run(() => HeavyMethod());
Debug.Log("Start end: " + Thread.CurrentThread.ManagedThreadId);
}
void HeavyMethod()
{
Debug.Log("Heavy Method: " + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(5000);
Debug.Log("Unity API Method: " + Thread.CurrentThread.ManagedThreadId);
transform.position = new Vector3(0, 0, 0);
}
以下のようなエラーが出ます。
UnityException: get_transform can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread when loading a scene.
Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
訳)
UnityException: get_transform はメインスレッドからのみ呼び出すことができます。
シーンをロードする際、コンストラクタとフィールド初期化子はロードスレッドから実行されます。
この関数はコンストラクタやフィールド初期化子では使用せず、代わりに初期化コードを Awake 関数または Start 関数に移動してください。
これが、Unity API が使えないというやつです。
結局こうするのね、というコード
Unity API を使う部分はマルチスレッドから出すしかありません。
async void Start()
{
Debug.Log("Start: " + Thread.CurrentThread.ManagedThreadId);
await Task.Run(() => HeavyMethod());
Debug.Log("Unity API Method: " + Thread.CurrentThread.ManagedThreadId);
transform.position = new Vector3(0, 0, 0);
Debug.Log("Start end: " + Thread.CurrentThread.ManagedThreadId);
}
void HeavyMethod()
{
Debug.Log("Heavy Method: " + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(5000);
}
注意として、Startがasync
となっているため、Start処理が終了する前にUpdateが始まります。
Start処理の中にUpdate内で使用するものを処理している場合、Start処理が完了していることを確認するコードが追加で必要です。
まとめ
非同期処理(async
、await
)とマルチスレッド(Task
)を使うことで、重い処理を別スレッドで動かせることが分かりました。
また、Task.Run()
で動かす処理の結果を待たなくていい場合は、非同期処理(async
、await
)はいらないです。
ちなみに、Task.Run()
で動かしても、以下の様に戻り値も帰ってきます。
async void Start()
{
Debug.Log("Start: " + Thread.CurrentThread.ManagedThreadId);
int x = await Task.Run(() => HeavyMethod());
Debug.Log(x);
Debug.Log("Unity API Method: " + Thread.CurrentThread.ManagedThreadId);
transform.position = new Vector3(0, 0, 0);
Debug.Log("Start end: " + Thread.CurrentThread.ManagedThreadId);
}
int HeavyMethod()
{
Debug.Log("Heavy Method: " + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(5000);
return 1;
}