Time.timeScaleを0にしたときawaitを使うと進行が止まるバグ【Unity】

はいどーもこんにちは、たまには記事でも書こうかなと思い立ったので書きます。
本日はUnityで遭遇したバグについてですね。
なお先に書いておくと、この不具合は2017.3.1p1以降で発生し、2018.2以降では修正されているようです。

参考URL:https://issuetracker.unity3d.com/issues/await-tasks-are-not-finished-if-timescale-equals-0?_ga=2.224704605.673433628.1536193228-151601528.1507631109

問題の経緯

ここでは問題の経緯を書きますが、解決方法等は次の項にあるので、興味のない方は飛ばしてください。

ゲームのセーブデータを作成するとき、僕は自分で作ったオレオレライブラリを使っています。
このライブラリはセーブデータを保存する際にシリアライズしたり、暗号化したりします。
で、このセーブデータ作成がどうもうまくいかないタイミングがあったのです(セーブデータ作成がうまくいかないなんて可能性は念頭に置いていなかったので、これに目をつけるだけでも相当時間がかかりました……)。
今までそんなことは一度もなかったのですが、ひとまず僕のプログラムの不具合だと思って修正箇所を探っていました。

するとどうも非同期処理をしている箇所(Taskを使ってasync/awaitしている箇所)が怪しいということが判明しました。
とどのつまりTaskの完了をawaitでずっと待っている状態になってしまっているのです。
これはデッドロックか」と思って、デバッグ作業を進めていると、どうも暗号化の処理の部分で止まっているようでした。
ですが暗号化の処理は既存のライブラリを使っています。
デッドロックが発生するようなコードにも思えませんでした。

そこで「他の場所ではセーブできるけど、特定のポイントでだけセーブがいつも失敗する」ことに着目し、どうもTime.timeScaleを0にしていることが他の箇所と違うぞと気が付きました。
で、試しにTime.timeScaleを1にしたま保存してみると……成功しました。

Time.timeScaleを0にすると発生する不具合

この不具合をまとめますと、Time.timeScaleを0にしてからawaitでタスクを待機したとき、そのタスクの結果を受け取れなくなるバグです。
つまりそのタスクについては完了しなくなります。
また、Time.timeScaleを0.01fとかにすると、明らかにその部分の処理速度が落ちます。

サンプルコード

この不具合を再現できるサンプルコードをは以下のような感じです。

このコードは「到達」という文字が表示されません。

原因と対処方法

Time.timeScaleを0にしたとき、Taskをawaitする処理がおかしくなるようです。
なのでawaitする前にTime.timeScale = 1f;にし、await終了後にTime.timeScale = 0f;に戻すことで僕は対処しました。
ですがこれだと問題が出る場合もあると思いますので、その場合は素直にUnity 2018.2以降に更新するのがよいと思います(つまり最新版では修正済み)。

終わりに

数時間はまった不具合だったので、メモがてらブログに残しておきました。
また、非同期処理関連には他にも不具合があったようです(こちらも2018.2以降で修正されているようです)。
https://issuetracker.unity3d.com/issues/backgroundworkers-completion-callback-doesnt-get-called-at-the-end-of-the-task?_ga=2.224704605.673433628.1536193228-151601528.1507631109

いやあ、ここまでたどり着くのに随分と遠回りをしました。
原因がasync/awaitとTaskだともっと早く気がつけていれば数時間もデバッグ作業をする必要はなかったのでしょうが、まあ仕方ないですね。
次に活かします。

ほなそんな感じでまた。

フォローする