HoloLensはUnityでアプリを作りますが、Unityエディターから直接実行ファイルを生成するのではなく、一度UWPへ変換してVisual Studioでビルド後にHoloLens実機に配置、実行する流れになります。
このため、Unityの.NET 3.5とUWPのコードが共存することになります。特に問題になるのが、UWPではasync/awaitを多用しますが、Unityの環境ではC#のバージョン的にそもそもasync/awaitが使えないため、コードの多くがこれを想定していません。
ここではUnityのアプリでasync/awaitを使う方法について紹介します。またasync/awaitでマルチスレッドになるので、同期の方法についても合わせて紹介します。おかしなところがあったら教えてください。
UWPではないUnityでのasync/awaitはこちらが詳しいです。
- neue cc - UnityのMonoアップグレードによるasync/awaitを更にUniRxで対応させる
- Unity 5.5でasync/await使えた話 | ++C++; // 未確認飛行 C ブログ
サンプルプログラム
UWPにビルドしたコード上でasync/awaitを使う方法を2つ紹介します。
- Task.Run()内でasync/awaitする(UnityEngine.WSA.Application.InvokeOnAppThreadは後述します)
- StartCoroutine()してyield return new WaitWhile()する
リポジトリはこちら
ちなみにUWP内のみコードを有効にするには #if UNITY_UWP をつけます。他にもいくつか定義がありますが、UWPビルドのどこかたタイミングで有効になるので、ビルドエラーになる場合もあります(細かくは調べていません)。
using System.Collections; using System.Collections.Generic; using UnityEngine; using System; #if UNITY_UWP using System.Threading.Tasks; #endif public class AsycnAwaitTest : MonoBehaviour { public TextMesh textMeshAynsc; public TextMesh textMeshCoroutine; // Use this for initialization void Start() { #if UNITY_UWP // Task.Runでasyncする Task.Run(async () => { UnityEngine.WSA.Application.InvokeOnAppThread(()=>{ textMeshAynsc.text = "Task.Run before : " + Time.time; }, true); await Task.Delay(5000); UnityEngine.WSA.Application.InvokeOnAppThread(() => { textMeshAynsc.text = "Task.Run after : " + Time.time; }, true); }); StartCoroutine(HeavyTask()); #endif } private IEnumerator HeavyTask() { #if UNITY_UWP // WaitWhileでまつ textMeshCoroutine.text = "Task.Run before : " + Time.time; var task = Task.Delay(5000); yield return new WaitWhile( () => !task.IsCompleted); textMeshCoroutine.text = "Task.Run after : " + Time.time; #else yield return null; #endif } }
Task.Run()内でasync/awaitする
Task.Run()で非同期タスクを起こし、この中のメソッドをasyncで定義すればその中ではawaitが使えます。これがシンプルだと思います。
StartCoroutine()してyield return new WaitWhile()する
StartCoroutine()で非同期処理を開始します。 コルーチンはasyncにできずawaitが使えないので、非同期メソッドのTaskを受け取りyield return new WaitWhile()で待ちます。従来のコードとの整合をとりつつ非同期メソッドを使う感じでしょうか。
マルチスレッド
上記2つの最大の違いはTask.Run()の場合はマルチスレッド、StartCoroutine()の場合はシングルスレッドになります(スレッドを生成せずメインスレッドを分割する)。
これによって何が変わるかというとTask.Run()内ではGameObjectへのアクセスができなくなります。WPFなどと同様にUIのオブジェクトはメインスレッドのみアクセスが可能です。そのため、ここで使っている3D TextのTextMeshをTask.Run()内で直接触ると例外が発生します。通常はSynchronizationContextで制御しますが、Unityにはこの仕組みがありません。
ではどうするかというと、UnityEngine.WSA.Application.InvokeOnAppThread()を使います。これによってアプリスレッド(Unityのメインスレッド)で処理されます。
もう一つ、UnityEngine.WSA.Application.InvokeOnUIThread()というのもありますが、こちらはXAMLのメインスレッドのようです。
ドキュメントはこちら docs.unity3d.com