
マイケル
みなさんこんにちは!
マイケルです!
マイケルです!

エレキベア
こんにちクマ〜〜〜

マイケル
今回はUnityの非同期処理ライブラリである
UniTask について触れていきます!
UniTask について触れていきます!

エレキベア
UniTaskって何なのクマ?

マイケル
簡単にいうと、Unityでいい感じにasync/awaitの非同期処理を行うためのライブラリで、
これを使うことで脱コルーチンすることができるんだ!
これを使うことで脱コルーチンすることができるんだ!

エレキベア
コルーチンの代わりにもなるクマね
それは楽しみクマ〜〜〜
それは楽しみクマ〜〜〜

マイケル
それじゃ早速みていこう!
UniTaskとは
最適化されたTask

マイケル
まず、C#には標準でasync/awaitを行うための Task という機能が用意されているのですが、
これはマルチスレッド前提で作られているため、メインスレッドでしか特定のAPIを実行できないUnityとは相性が悪かったみたいです。
そのような経緯でよりUnityに最適化して作られたライブラリがUniTaskになります。
これはマルチスレッド前提で作られているため、メインスレッドでしか特定のAPIを実行できないUnityとは相性が悪かったみたいです。
そのような経緯でよりUnityに最適化して作られたライブラリがUniTaskになります。

エレキベア
C#標準にも同じようなのがあるのクマね

マイケル
ちなみにTaskは標準だと呼び出す度に実行するスレッドが変わるみたいだけど、
Unityではスレッドが変わらないよう制御はされているみたいです。
そのため使えるには使えるのですが、内部ではスレッド切り替えを行なっているようなので効率が悪いみたいですね・・・。
Unityではスレッドが変わらないよう制御はされているみたいです。
そのため使えるには使えるのですが、内部ではスレッド切り替えを行なっているようなので効率が悪いみたいですね・・・。

マイケル
この辺りの詳しい話は下記スライドがとても分かりやすいので見てみてください!

エレキベア
中々面白いスライドクマね

マイケル
それではTaskについて軽く触れたところでUniTaskに戻りますが、
簡単な例としては下記のようにして非同期処理を実装することができます。
簡単な例としては下記のようにして非同期処理を実装することができます。
public class UniTaskSample01 : MonoBehaviour
{
private async void Start()
{
await HelloWorldTask();
}
private async UniTask HelloWorldTask()
{
await UniTask.Delay(1000);
Debug.Log("Hello, World");
}
}
↑1秒待ってログ出力する処理
マイケル
UniTaskを戻り値として定義すると、その関数を非同期処理として定義することができます。
そのタスクをasyncを付けた関数の中でawaitすることで、処理が完了するのを待機することができるというわけです!
そのタスクをasyncを付けた関数の中でawaitすることで、処理が完了するのを待機することができるというわけです!

エレキベア
async -> 非同期
await -> 待機する
というわけクマね
await -> 待機する
というわけクマね

マイケル
Unityのコルーチンと使い方が似ていると感じる人も多いと思います。
比べた時にどのようなメリットがあるのかについて見ていきましょう!
比べた時にどのようなメリットがあるのかについて見ていきましょう!
コルーチンとの比較

マイケル
Unityのコルーチンは下記のように使うことができます。
using System;
using System.Collections;
using UnityEngine;
namespace UniTaskTest
{
public class CoroutineSample01 : MonoBehaviour
{
private void Start()
{
StartCoroutine(HelloWorldCoroutine());
}
private IEnumerator HelloWorldCoroutine()
{
yield return new WaitForSeconds(1.0f);
Debug.Log("Hello, World");
}
}
}
↑コルーチンの実行
エレキベア
お馴染みの処理クマね

マイケル
この処理だけだと先ほどのUniTaskの例とあまり変わらないかもしれませんが、
非同期処理が完了した後に別の処理を行いたい場合はどのように記述すればいいでしょうか?
非同期処理が完了した後に別の処理を行いたい場合はどのように記述すればいいでしょうか?

マイケル
これを実装しようとすると、恐らく下記のようになると思います。
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Events;
namespace UniTaskTest
{
public class CoroutineSample02 : MonoBehaviour
{
private void Start()
{
StartCoroutine(HelloWorldCoroutine(() => Debug.Log("Finish!!")));
}
private IEnumerator HelloWorldCoroutine(UnityAction callback)
{
yield return new WaitForSeconds(1.0f);
Debug.Log("Hello, World");
callback(); // コールバック実行
}
}
}
↑非同期処理の完了後に別の処理を行う
マイケル
引数としてコールバックを渡して、処理が完了したら呼び出す方式です。
実装自体は簡単ですが、複数の非同期処理が重なっていくと処理がネストし、可読性もよくないというデメリットがあります。
実装自体は簡単ですが、複数の非同期処理が重なっていくと処理がネストし、可読性もよくないというデメリットがあります。

エレキベア
コールバック地獄に陥ってしまうクマね

マイケル
同じ処理をUniTaskで実行すると下記のようになります。
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace UniTaskTest
{
public class UniTaskSample01 : MonoBehaviour
{
private async void Start()
{
await HelloWorldTask();
// コールバックを続けて書ける
Debug.Log("Finish!!");
}
private async UniTask HelloWorldTask()
{
await UniTask.Delay(1000);
Debug.Log("Hello, World");
}
}
}
↑非同期処理の完了後に別の処理を行う(UniTask版)
マイケル
非同期処理の実行後に続けて書けるため、より自然に感じるのではないでしょうか?
非同期処理を多用する場合にコンパクトに記述することができるのは、async/awaitを使う大きなメリットです。
非同期処理を多用する場合にコンパクトに記述することができるのは、async/awaitを使う大きなメリットです。

マイケル
また、UniTaskには戻り値を定義できる機能も備わっているというのも魅力の一つです。

エレキベア
これはコルーチンよりUniTaskの方が断然いいクマね

マイケル
一点、コルーチンと比べた時のデメリットとして、オブジェクトのDestroy時に非同期処理が破棄されない点については注意が必要です。
こちらはキャンセルトークンという仕組みを使って実装しますが、この後みていきましょう!
こちらはキャンセルトークンという仕組みを使って実装しますが、この後みていきましょう!

エレキベア
まあMonoBehaviourの機能ではないから当然といえば当然クマね・・・
UniTaskの基本的な使い方

マイケル
それではUniTaskの基本的な使い方について見ていきます!
導入方法

マイケル
UniTaskの導入はGitHubから行います。
manifest.jsonに追記してもいいですし、UPM経由でインストールしてもOKです!
詳しくはGitHubのREADMEをご参照ください!
manifest.jsonに追記してもいいですし、UPM経由でインストールしてもOKです!
詳しくはGitHubのREADMEをご参照ください!

エレキベア
導入は一瞬クマね
シンプルな使い方

マイケル
まず、最もシンプルな使い方についておさらいです。
先ほど紹介した通り、下記のようにasync/awaitで処理を実行することができます。
先ほど紹介した通り、下記のようにasync/awaitで処理を実行することができます。
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace UniTaskTest
{
public class UniTaskSample01 : MonoBehaviour
{
private async void Start()
{
await HelloWorldTask();
// コールバックも続けて書ける
Debug.Log("Finish!!");
}
private async UniTask HelloWorldTask()
{
await UniTask.Delay(1000);
Debug.Log("Hello, World");
}
}
}
↑シンプルな処理
マイケル
そしてUniTask<型>と記述することで戻り値の型を指定することができます。
下記は処理終了後に文字列を返却する例です。
下記は処理終了後に文字列を返却する例です。
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace UniTaskTest
{
public class UniTaskSample02 : MonoBehaviour
{
private async void Start()
{
var message = await HelloWorldTask();
Debug.Log(message);
}
private async UniTask<string> HelloWorldTask()
{
await UniTask.Delay(1000);
Debug.Log("Hello, World");
return "Finish!!";
}
}
}
↑戻り値も指定できる
エレキベア
非同期処理で戻り値が指定できるのは便利クマね
asyncメソッド以外から実行する

マイケル
asyncメソッド以外から、非同期処理を呼び出すこともできます。
その場合、関数実行時にForget()を付けないと警告が出てしまうため注意しましょう。
その場合、関数実行時にForget()を付けないと警告が出てしまうため注意しましょう。
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace UniTaskTest
{
public class UniTaskSample03 : MonoBehaviour
{
private void Start()
{
HelloWorldTask().Forget();
// awaitしていないためこちらが先に呼ばれる
Debug.Log("not Finish!!");
}
private async UniTask HelloWorldTask()
{
await UniTask.Delay(1000);
Debug.Log("Hello, World");
}
}
}
↑async以外から呼び出す
マイケル
また、もちろんですがawaitしていないためその後の処理は続けて実行されることにも注意しましょう。

エレキベア
awaitしないと止まらないクマね
複数の非同期処理を待機する

マイケル
便利な機能として、WhenAllがあります。
下記のように実装することで複数の非同期処理の結果を待機することができます。
下記のように実装することで複数の非同期処理の結果を待機することができます。
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace UniTaskTest
{
public class UniTaskSample04 : MonoBehaviour
{
private async void Start()
{
// 複数の非同期処理を待機して結果を受け取る
var (msg1, msg2, msg3)
= await UniTask.WhenAll(GetMessage1(), GetMessage2(), GetMessage3());
Debug.Log(msg1 + msg2 + msg3);
}
private async UniTask<string> GetMessage1()
{
await UniTask.Delay(1000);
return "Message1";
}
private async UniTask<string> GetMessage2()
{
await UniTask.Delay(2000);
return "Message2";
}
private async UniTask<string> GetMessage3()
{
await UniTask.Delay(3000);
return "Message3";
}
}
}
↑複数の非同期処理の待機
エレキベア
これはロード処理なんかで使えそうクマね
非同期処理を中断する

マイケル
最後に、非同期処理を中断する方法についてです。
下記のようにnew CancellationTokenSource()でキャンセル用のトークンを生成してawaitする非同期処理に渡してあげることで、Cancel()が実行されたタイミングで処理を中断することができます。
下記のようにnew CancellationTokenSource()でキャンセル用のトークンを生成してawaitする非同期処理に渡してあげることで、Cancel()が実行されたタイミングで処理を中断することができます。
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace UniTaskTest
{
public class UniTaskSample05 : MonoBehaviour
{
private CancellationTokenSource _cancellationTokenSource;
private void Start()
{
_cancellationTokenSource = new CancellationTokenSource(); // キャンセルトークン生成
LongWaitTask(_cancellationTokenSource.Token).Forget();
Destroy(gameObject);
}
private void OnDestroy()
{
// Destroy時にキャンセルする
_cancellationTokenSource?.Cancel();
}
private async UniTask LongWaitTask(CancellationToken token)
{
await UniTask.Delay(3000)
.WithCancellation(token); // トークンを指定
// キャンセルされるとその後の処理も実行されない
Debug.Log("finish!!");
}
}
}
↑キャンセルトークンを使った中断処理
マイケル
先ほど話した通り、コルーチンはオブジェクト破棄時に非同期処理も破棄されるのに対して、
UniTaskは自身で中断処理を定義しないといけない ため注意しましょう!
UniTaskは自身で中断処理を定義しないといけない ため注意しましょう!

エレキベア
ここは中々面倒くさいクマね・・・

マイケル
また、上記のようにOnDestroyでキャンセルさせる場合には、簡略化したGetCancellationTokenOnDestroyといった拡張メソッドもあるため、可能ならこちらを使用する方が楽そうですね・・・。
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace UniTaskTest
{
public class UniTaskSample06 : MonoBehaviour
{
private void Start()
{
var token = this.GetCancellationTokenOnDestroy(); // キャンセルトークン生成
LongWaitTask(token).Forget();
Destroy(gameObject);
}
private async UniTask LongWaitTask(CancellationToken token)
{
await UniTask.Delay(3000)
.WithCancellation(token); // トークンを指定
// キャンセルされるとその後の処理も実行されない
Debug.Log("finish!!");
}
}
}
↑OnDestroyでキャンセルする処理を簡略化した場合
エレキベア
これならまだ許せるクマ
UniTrackerでの状態監視

マイケル
基本的な使用方法についての紹介は以上になりますが、
UniTaskの状態を可視化できるUniTrackerについても紹介しておきます。
UniTaskの状態を可視化できるUniTrackerについても紹介しておきます。

マイケル
こちらは「Window → UniTask Tracker」から開くことができ、
下記のように各タスクの状態を監視することができます。
下記のように各タスクの状態を監視することができます。


エレキベア
状態が分かるのは便利クマね

マイケル
実行中のTaskが残っているかどうかについても
これでチェックができそうだね。
これでチェックができそうだね。
おわりに

マイケル
というわけで今回はUniTaskの紹介でした!
どうだったかな??
どうだったかな??

エレキベア
早くasync/awaitを使いたいクマ〜〜〜〜!!

マイケル
より詳しく知りたい方は、専用の書籍も出ているようなので
こちらも読んでみるといいかもしれません!
こちらも読んでみるといいかもしれません!
UniRx/UniTask完全理解 より高度なUnity C#プログラミング

エレキベア
つよつよプログラマ・・・

マイケル
それでは今日はこの辺で!
アデューー!!
アデューー!!

エレキベア
クマ〜〜〜〜〜
【Unity】UniTaskとasync/awaitを使って非同期処理を実装する【脱コルーチン】〜完〜
コメント