#悩ましいスレッド問題
一般的なスレッド問題といえばデッドロック系の話、GUIを伴うアプリではメインスレッドとサブで使うスレッド間の問題。。。などなどあるかと思います。
ここ数か月HoloLensのサンプルアプリを開発していてこのスレッド周りが普段作っているアプリの中でもとびぬけて面倒というかわかりにくかったので備忘録として残しておきます。
HoloLensでUnity/UWP両方がっつり使うときには見ていただくと少しは幸せになるかもしれません。
HoloLensの開発で直面したもの
いくつか投稿しているサンプルの中で私の場合よくUWP側の実装を使います。理由がUnityわからないからというよこしまな部分もあるのですが、Microsoftの各サービスとの連携を考えると必然的にUWP寄りに実装するほうがシームレスです。
例えばCognitive Service APIなんかはもはやクラスのインスタンス化と1メソッド呼ぶだけで連携できてしまうくらいの手軽さです。
何が問題になるのか
例えばUnityでがっつり作っていてUWPはただビルドで吐き出して未変更でデプロイするなら問題になりにくいとおもいます。問題になるのはUnityとUWPの両方で実装するパターンです。
UnityにしてもUWPにしても、基本的なスレッドの考え方は一般的なスレッドの構成と同じだと思います。
つまりプロセスを起動すると実行される「メインスレッド」と「その都度呼び出されるスレッド」の2系統です。
このメインスレッドは使うアプリケーションのタイプによっていろいろ呼び方があると思うのですが、UWP等.NETでは画面が伴うアプリケーションでは「UIスレッド」という表現が多いかと思います。
HoloLensでUnityとUWP両方を使う場合ちょっとした問題が起こります。UnityはUnityで「Appスレッド」というUnity側でのメインスレッド(?)がありその上で処理が動いています。ですが、先に少し話したUWPなどの.NETの一部の機能は「UIスレッド」上での動作を要求します。つまりUWPの機能を利用するときはUIスレッドを使用しUnity側のライブラリであればAppスレッドを使用する必要が出てきます。図にするとこういう関係があることがわかります。
対応方法
この問題の解決策はそれほど難しいわけではなくAppCallBacksクラスを利用して適切なスレッドでアプリを動かせは解決は可能です。どう切り替えるかについては比較的わかりやすいです。ある程度対処療法的にもできるのですが以下の要素についてはまず間違いなくそれぞれのスレッドでの動作が必要になります。
- 画面オブジェクトの操作(ex:表示テキストの変更、座標変更など)
- リソースアクセス操作(ファイルI/Oなど)
- デバイス関連(カメラなど)
上記の機能をUnityのライブラリでやればAppスレッド上で、UWPのライブラリでやればUIスレッドでとなります。
対処パターン
私がよく使っているパターンとしてUWP側でリソース系の処理を行った後にその結果をUnityの画面に出すというものです。この場合、UWP側なのでUIスレッドを使用します。UWP側の処理が終わってからAPPスレッドでUnityの画面の処理を呼ぶ実装を入れることが多いです。
// Copyright(c) 2017 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php
AppCallbacks.Instance.InvokeOnUIThread(async () =>
{
//UIスレッド(UWP)側での対処が必要な処理
AppCallbacks.Instance.InvokeOnAppThread(() => {
//Appスレッド(Unity)側での対処が必要な処理
}, false);
}, true);
同期/非同期での問題
基本的にマルチスレッドを想定した処理を書くように気を付ければいいのですが、「HoloLensでCognitive Service APIを使ってみたー困ったことー」に記載したような予想しえない問題も時々出てきます。カメラからスクリーンショットをとるためのメソッドなのですが非同期メソッドのためawaitで待機させてもエラーになります。キャプチャデータをストリームに書き込んでいるんだと思うのですが。。。対処方法としてはawaitではなく同期するように処理を変更することで対処できました。
最後に
マルチスレッドになる状況での実装ではいろいろ気を配っておかないと思わぬ動作になります。それぞれ見極めが必要ですが基本的には「リソースなどの次の処理までに確実に終わってほしい処理」のみ同期処理を行い、それ以外は非同期とすれば
おおむね解決すると思います。その時に「この処理はUnity?UWP?」と問いかけてみて正しいスレッドで処理するように気を付ければよいと思います。