Unityでマルチスレッド処理する方法が多すぎて混乱したのでざっくりまとめました。
- C#におけるマルチスレッドの基本:Task
- 簡単な同期的並列処理ならParallelで
- Linqの処理をマルチスレッドで行うAsParallel
- UniTaskでもマルチスレッド処理できる
- UniRxでもマルチスレッド処理できる
- 大量のデータを高速に処理するUnityのJobSystem
- 関連
- 参考
Unity2020.1.10
C#におけるマルチスレッドの基本:Task
C#でマルチスレッドの処理を行う基本的な方法としてTaskが挙げられます。
以下のようにTask.Run
でTaskを作れば引数に渡した処理を別スレッドに逃がせます。
using System.Threading; using System.Threading.Tasks; using UnityEngine; public class Example : MonoBehaviour { private void Start() { Task.Run(() => { // 別スレッドでやりたい処理 Debug.Log(Thread.CurrentThread.ManagedThreadId); }).Wait(); } }
Taskのファクトリとしては上記のRunの他にTask.Factory.StartNew()
も使えます。
これらでTaskをいくつか作ってTask.WhenAll()
に渡せばそれらをマルチスレッドで処理して終了を待つTaskが作れます。
using System.Threading; using System.Threading.Tasks; using UnityEngine; public class Example : MonoBehaviour { private void Start() { var task1 = Task.Run(() => { // 処理1 Debug.Log(Thread.CurrentThread.ManagedThreadId); }); var task2 = Task.Run(() => { // 処理2 Debug.Log(Thread.CurrentThread.ManagedThreadId); }); var task3 = Task.Run(() => { // 処理3 Debug.Log(Thread.CurrentThread.ManagedThreadId); }); Task.WhenAll(task1, task2, task3).Wait(); } }
簡単な同期的並列処理ならParallelで
戻り値のない複数のメソッドをマルチスレッドで処理して、完了を同期的に待ちたい場合にはParallelクラスが使えます。
以下の例では処理1~3がそれぞれ別のスレッドで並列実行されます。
using System.Threading; using System.Threading.Tasks; using UnityEngine; public class Example : MonoBehaviour { private void Start() { Parallel.Invoke(() => { // 処理1 Debug.Log(Thread.CurrentThread.ManagedThreadId); }, () => { // 処理2 Debug.Log(Thread.CurrentThread.ManagedThreadId); }, () => { // 処理3 Debug.Log(Thread.CurrentThread.ManagedThreadId); }); } }
ちなみにParallelクラスにはこのほかにもParallel.For()
やParallel.ForEach()
といったメソッドも存在します。
Linqの処理をマルチスレッドで行うAsParallel
Linqの処理をマルチスレッドで行うには以下のようにAsParallelを使います。
using System.Linq; using System.Threading; using UnityEngine; public class Example : MonoBehaviour { private void Start() { var strings = Enumerable .Range(0, 3) .AsParallel() // このIEnumerableの評価をマルチスレッドで並列処理する .Select(x => { // これはマルチスレッドで行う Debug.Log($"A: {Thread.CurrentThread.ManagedThreadId}"); return x.ToString(); }) .AsSequential() // これ以降の処理は並列で行わない .Select(x => { // この処理は並列化されない(必ずしも元のスレッドで行うという意味ではない) Debug.Log($"B: {Thread.CurrentThread.ManagedThreadId}"); return x; }) .ToArray(); } }
上記のようにAsSequentialを使えばそれ以降は並列処理を取りやめることもできます。
ただしこれは並列に処理をしないというだけで必ずしも元のスレッドに処理を戻すというわけでは無いようです。
UniTaskでもマルチスレッド処理できる
UniTaskはUnityで非同期処理を行うためのライブラリです。
UniTaskでは以下のように書くことで処理を別スレッドに逃がせます。
using System.Threading; using Cysharp.Threading.Tasks; using UnityEngine; public class Example : MonoBehaviour { private async void Start() { await UniTask.WhenAll(FooAsync(), FooAsync(), FooAsync()); } private async UniTask FooAsync() { // ここからはサブスレッドで処理 await UniTask.SwitchToThreadPool(); Debug.Log(Thread.CurrentThread.ManagedThreadId); // ここからはメインスレッドに戻す await UniTask.SwitchToMainThread(); } }
UniRxでもマルチスレッド処理できる
UniRxはUnityでReactive Extensionsの概念を扱うためのライブラリです。
以下のようにObservable.Start
などを使えばUniRxでマルチスレッドの処理ができます。
using System; using System.Threading; using UniRx; using UnityEngine; public class Example : MonoBehaviour { private void Start() { Observable .Merge(FooAsObservable(), FooAsObservable(), FooAsObservable()) .Subscribe() .AddTo(this); } private IObservable<Unit> FooAsObservable() { return Observable.Start(() => Debug.Log(Thread.CurrentThread.ManagedThreadId)); } }
UniRxにおけるスレッドの取り扱いは以下にまとめているので必要に応じて参照してください。
(昔の記事なので参考程度に)
大量のデータを高速に処理するUnityのJobSystem
JobSystemはUnityが開発した、Unityでマルチスレッド処理を行うための仕組みです。
メモリ上に連続的にデータを配置するNativeArrayやBurst Compilerというコンパイラにより、
例えば頂点座標や色情報など多くのデータを高速で処理することが可能になります。
これに関しては説明が長くなりすぎるので別記事にまとめる予定です。