UnityのMonoアップグレードによるasync/awaitを更にUniRxで対応させる
- 2016-10-05
ついに!.NET 4.6アップグレードが始まりました。Unityの。Unity 5.5でC#コンパイラをアップグレードしていましたが、今回はついにフレームワークも、です。また、Unity 5.5のものはC#のバージョンは4に制限されていましたが、今回はC# 6が使えます。現在はForumでアーリアクセスバージョンが公開されていて、ついでにそこでリンクされているVisual Studio Tools for Unityも入れると、かなりふつーに.NET 4.6, C# 6対応で書ける感じです。
さて、.NET 4.6, C# 6といったら非同期。async/await。もちろん、書けました。が、しかし。
async Task ThraedingError()
{
Debug.Log($"Start ThreadId:{Thread.CurrentThread.ManagedThreadId}");
await Task.Delay(TimeSpan.FromMilliseconds(300));
Debug.Log($"From another thread, can't touch transform position. ThreadId:{Thread.CurrentThread.ManagedThreadId}");
Debug.Log(this.transform.position); // exception
}
これはtransformのとこで例外でます。なんでかっていうと、awaitによって別スレッドに行ってしまっているから。へー。この辺、async/awaitではSynchronizationContextという仕組みで制御するのですが、現在のUnity標準では特に何もされてないようです。
それだけだとアレなので、そこで出てくるのがUniRx。今日、アセットストアで最新バージョンのVer 5.5.0を公開したのですが、この5.5.0ではasync/await対応を試験的に入れています。それによって自動的にSynchronizationContextも生成/登録してくれます。
async Task UseUniRxInBackground()
{
Debug.Log($"Start ThreadId:{ Thread.CurrentThread.ManagedThreadId}");
await Task.Delay(TimeSpan.FromMilliseconds(300));
Debug.Log($"From same thread, because UniRx installs UniRxSynchronizationContext.ThreadId:{ Thread.CurrentThread.ManagedThreadId}");
Debug.Log(this.transform.position); // show transform
}
というように、UniRxをインポート後では、前の例外を吐いたコードと全く同じでも、ちゃんとメインスレッドに戻してくれるようになります。
Coroutine is awaitable
UniRxを入れることで追加される機能はそれだけではなく、更に普通のコルーチンもawait可能な仕組みを裏側で仕込んでいます。これにより
async Task CoroutineBridge()
{
Debug.Log("start www await");
var www = await new WWW("https://unity3d.com");
Debug.Log(www.text);
await CustomCoroutine();
Debug.Log("await after 3 seconds");
}
IEnumerator CustomCoroutine()
{
Debug.Log("start wait 3 seconds");
yield return new WaitForSeconds(3);
Debug.Log("end 3 seconds");
}
といったように、WWWとかIEnumeratorを直接awaitすることが可能になります。これはUniRx側で用意した仕組みによるものなので、普通では(現状は)できません。
勿論(?)IObservableもawait可能になっています。
async Task AwaitObservable()
{
Debug.Log("start await observable");
await Observable.NextFrame(); // like yield return null
await Observable.TimerFrame(5); // await 5 frame
try
{
// ObservableWWW promote exception when await(difference in await WWW)
var result = await ObservableWWW.Get("https://404.com");
Debug.Log(result);
}
catch (WWWErrorException ex)
{
Debug.LogError(ex.ToString());
}
Debug.Log("end await observable");
}
ObservableWWWを使うと例外はちゃんとtry-catchのほうに投げてくれるようになって、より自然に、簡単に扱えるようになります。
まとめ
思ったよりも、普通に使えて、普通に統合できるな、という印象があります < async/await。コルーチンで扱うよりも自然で、より強力なので、非同期を扱うのに適したシチュエーションではこっちのほうが良いのは間違いないはずです。Rxとの住み分けですが、基本的に非同期が対象ならばasync/awaitのほうが良いです。が、今回見ていただいたようにIObservableはawaitableなので、コードがRxになっているならば、現在のコードから自然にasync/awaitベースにソフトに移行することが可能でしょう。
Unityが今後、標準でSynchronizationContextを入れてくるのか、コルーチン対応クラスをawait対応にさせてくるのか、などはちょっと分かりません。分かりませんが、UniRxならば、その対応がずっと後のことになるとしても、今すぐ問題なく使うことが出来ますし、その可能性を今すぐ感じ取ることが可能なので、ぜひとも試してみてください!
余談
UniRxがAssetStore STAFFPICKに選ばれましたー。
うーん、嬉しい。