アセットストアにリリースしている Fast Shadow Receiver というアセットでは、自分でスレッドプールを作成してマルチスレッドで動かしていた(Unityでマルチスレッドプログラム参照)のですが、Unityでもジョブシステムが導入されたので、遅れ馳せながらジョブを使うように変更しました。Unityがもつスレッドプールと自前のスレッドプールが両方存在するとスレッドの奪い合いが起きて効率が悪くなっちゃうので。

ただ、Unity のジョブシステムにはひとつ困ったことがあって、参照型を渡すことができないのです。たぶん、Unity のジョブキューはネイティブコードで実装されていて、マネージドのポインタをジョブキューに渡してしまうと、ジョブが実行される前にガーベッジコレクションなどでポインタの位置が変わってしまう可能性があって、とてつもなく危険だからでしょう。

ジョブシステムの典型的な使い方としては、NativeArray を使ってジョブとデータの受け渡しをするみたいですが、これだけだと出来ることが本当に限られてしまってとても不便です。

なんとかならないものかと探してみたら、GCHandleという便利なものがありました。この構造体はマネージドのポインタをネイティブコードに安全に渡すためのものなので、まさに望み通りの代物です。

使い方も簡単で、こんな風に使えます。

GCHandle.Alloc で GCHandle を作成して、m_jobHandle.Target で参照型を取り出します。m_jobHandle.Free() を忘れるとメモリリークするのでご注意ください。

ジョブを呼び出すときは、

みたいにします。これで jobFunction がジョブスレッドで実行されるんですが、ジョブと同期を取るために、JobHandle を保持しておく必要があります。

でも、単発でジョブを実行したいときなんかに、いちいち JobHandle を保持しておくのも面倒なので、次のようなユーティリティークラスを作ることにしました。

JobHandle をユーティリティークラスの Dictionary に保存しておいて、いちいち使う側が JobHandle を保持しなくてすむようになっています。GCHandle の使い方ということで紹介しましたが、System.Actionデリゲートに使いたい参照型のデータをバインドしておけば、これだけで好き放題できるので、なかなか便利です。注意点としては、デリゲートを毎回作成してしまうと GCAlloc が発生するので、System.Action 型の変数に保持してから使った方がいいのと、ジョブスレッドで参照型が使えるとは言っても、その参照型がスレッドセーフになっているかは別問題なので、十分注意が必要ということです。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

Anti Spam Code *