最初に
スタジオしまづというUnityのオンラインコミュニティで、勉強会をします。
その資料で、UniTaskについて軽くまとめたものになります。
目次
- 同期処理について
- 非同期処理について
- コルーチンの復習
- await / asyncについて
- スレッド処理について
- UniTaskの面倒なところ
- おわりに
同期処理について
複数の処理を実行するときに、一つずつ順番に処理が実行される
using UnityEngine;
// ======================================================================
// 同期処理について
// ======================================================================
public class Sample01 : MonoBehaviour
{
void Start()
{
A();
}
void A()
{
Debug.Log("A");
B();
Debug.Log("C");
}
void B()
{
Debug.Log("B");
}
}
実行結果は A → B → C
非同期処理について
複数の処理を実行するときに、実行結果の前に処理が戻ってくる
using System.Collections;
using UnityEngine;
// ======================================================================
// 非同期処理
// ======================================================================
public class Sample02 : MonoBehaviour
{
void Start()
{
A();
}
void A()
{
Debug.Log("A");
StartCoroutine(B());
Debug.Log("C");
}
IEnumerator B()
{
yield return new WaitForSeconds(1);
Debug.Log("B");
}
}
コルーチンの復習
using System.Collections;
using UnityEngine;
// ======================================================================
// コルーチンの復習
// ======================================================================
public class Sample03 : MonoBehaviour
{
void Start()
{
StartCoroutine(SampleCoroutine());
}
IEnumerator SampleCoroutine()
{
Debug.Log("スタート");
// 2秒待機する
yield return new WaitForSeconds(2f);
Debug.Log("ゴール");
}
}
こんな感じで、2秒後に〇〇するみたいな処理ができます。
コルーチンにも欠点があり、
- 関数に戻り値が持たせられない(厳密にはできる)
- MonoBehaviourを継承していないと使えない
- ゲームオブジェクトが破壊や非表示されると処理が止まる
などがあります。
await / asyncについて
await / acyncというのは、C#にある非同期処理の機能です。
コルーチンと違い、戻り値を持たせられるし、
MonoBehaviourがなくても使えます。
ただし、ゲームオブジェクトが破壊されても動くので、
注意が必要です。(後述します。)
using UnityEngine;
using Cysharp.Threading.Tasks;
using System;
// ======================================================================
// お試しUniTask
// ======================================================================
public class Sample04
{
async void Start()
{
await Test();
}
async UniTask Test()
{
Debug.Log("スタート");
// 2秒待機する
await UniTask.Delay(TimeSpan.FromSeconds(2));
Debug.Log("ゴール");
}
}
コルーチンをawait / asyncに置き換えるとこんな感じです。
using UnityEngine;
// ======================================================================
// お試しUniTaskその2
// ======================================================================
public class Sample05 : MonoBehaviour
{
async void Start()
{
Sample05_2 sample05_2 = new Sample05_2();
// Test()がreturnするまで待機する
var str = await sample05_2.Test();
Debug.Log(str);
}
}
using System;
using Cysharp.Threading.Tasks;
using UnityEngine;
public class Sample05_2
{
public async UniTask<string> Test()
{
Debug.Log("スタート");
// 2秒待機する
await UniTask.Delay(TimeSpan.FromSeconds(2));
return "ゴール";
}
}
戻り値を持たせるとこんな感じです。
ちなみに、
var str = await sample05_2.Test();
awaitというのは処理が終わるまで待ってくれるという意味です。
このawaitを書かないと、"str"が空のまま出力されてしまいますね。
スレッド処理について
スレッド処理というのは、プログラムを実行するときに
複数でマルチスレッドとして処理させたり、
単数でシングルスレッドとして処理できます。
例えばゲーム制作だと、
ゲームの中の計算をする処理と
ゲームのグラフィックを描画する処理があると思います。
Unityで普通に製作していると、シングルスレッドで
この2つの処理を同時に動かしているので、
計算の処理が複雑になったりすると重くなってしまいます。
そのため、計算部分は別のスレッドにやってもらおう!
みたいな使い方ができます。
詳しくは書かないですが、C#にはTaskというスレッド処理の機能が元々あります。
Unityではそのまま使うのが大変な機能でしたが、それを使いやすくしてくれたのがUniTaskに
なります。
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
// ======================================================================
// お試しスレッド処理
// ======================================================================
public class Sample06 : MonoBehaviour
{
async void Start()
{
Debug.Log(Thread.CurrentThread.ManagedThreadId);
// 処理するスレッドの変更
await UniTask.SwitchToThreadPool();
Debug.Log(Thread.CurrentThread.ManagedThreadId);
// 処理するスレッドをメインスレッドに変更
await UniTask.SwitchToMainThread();
Debug.Log(Thread.CurrentThread.ManagedThreadId);
}
}
簡単にですが、これで処理するスレッドを切り替えています。
実はメインスレッドでないと、Unityの標準のtransformとかが
使えなかったりしますが、今回は軽い紹介にします。
UniTaskの面倒なところ
最後にUniTaskの面倒なところをまとめます。
これがあるから個人開発はコルーチンのままでいいのかなーってなっちゃうくらいですね。
前述の通り、UniTaskの処理はゲームオブジェクトが破壊されても動きます。
そのため破壊されたら処理を止めたい場合、自分でその処理を書かないといけないです。
今回は左クリックしたら自身のゲームオブジェクトを破壊し、処理を
止めるというコードを書きます。
using UnityEngine;
using Cysharp.Threading.Tasks; // UniTaskに必要
using System.Threading; // CancelToken系に必要
using System;
// ======================================================================
// お試し中断処理
// ======================================================================
public class Sample07 : MonoBehaviour
{
private CancellationTokenSource _cts = new CancellationTokenSource();
async void Start()
{
Debug.Log("Start");
// トークンソースからトークンを生成
CancellationToken token = _cts.Token;
// 非同期が止まったときの例外を書かないとエラーになる
try
{
await A(token);
}
catch (OperationCanceledException)
{
Debug.Log("処理を強制的に終わらせたよ");
}
}
// 5秒待機するUniTask
async UniTask A(CancellationToken token)
{
await UniTask.Delay(5000, false, PlayerLoopTiming.Update, token);
Debug.Log("待てたよ");
}
void Update()
{
// クリックしたらキャンセル
if (Input.GetMouseButtonDown(0))
{
Destroy(gameObject);
}
}
// gameobject破壊時にメモリの開放
void OnDestroy()
{
_cts.Cancel();
_cts.Dispose();
}
}
僕もあまりなれていないですが、普段使わないようなDispose()でのメモリの開放や、
try-catch文での例外処理など、個人開発ではあまり使わない処理を書かないといけないです。
逆を言えば、こういうC#の機能の勉強をしたい方がUniTaskに取り組むのも良いかもしれない
ですね。
おわりに
勉強会用の資料ということで、まとめてみました。
何かしら間違っている部分があったら申し訳ないです。
その時は教えていただけると助かります!