とりあえずUniTask学んでみたのでまとめてみる

# 最初に
スタジオしまづというUnityのオンラインコミュニティで、勉強会をします。
その資料で、UniTaskについて軽くまとめたものになります。

# 目次
- 同期処理について
- 非同期処理について
- コルーチンの復習
- await / asyncについて
- スレッド処理について
- UniTaskの面倒なところ
- おわりに

# 同期処理について
複数の処理を実行するときに、一つずつ順番に処理が実行される

```C#:@Sample01
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

図にするとこんな感じ
![1-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2631403/bda25948-818c-5a1a-a636-fd334f77b023.png)

# 非同期処理について
複数の処理を実行するときに、実行結果の前に処理が戻ってくる

```C#:@Sample02
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");
    }
}
```
実行結果は A → C → B
図にするとこんな感じ
![333.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2631403/96155ccf-1525-761f-91cf-45cb054d2384.png)

# コルーチンの復習

```C#:@Sample03
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("ゴール");
    }
}
```
こんな感じで、２秒後に〇〇するみたいな処理ができます。


コルーチンにも欠点があり、
- 関数に戻り値が持たせられない（厳密にはできる）
- MonoBehaviourを継承していないと使えない
- ゲームオブジェクトが破壊や非表示されると処理が止まる

などがあります。

# await / asyncについて

await / acyncというのは、C#にある非同期処理の機能です。

コルーチンと違い、戻り値を持たせられるし、
MonoBehaviourがなくても使えます。
ただし、ゲームオブジェクトが破壊されても動くので、
注意が必要です。（後述します。）

```C#:@Sample04
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に置き換えるとこんな感じです。

```C#:@Sample05

using UnityEngine;

// ======================================================================
// お試しUniTaskその２
// ======================================================================
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 "ゴール";
    }
}
```

戻り値を持たせるとこんな感じです。

ちなみに、
```C#
var str = await sample05_2.Test();
```
awaitというのは処理が終わるまで待ってくれるという意味です。
このawaitを書かないと、"str"が空のまま出力されてしまいますね。

# スレッド処理について

スレッド処理というのは、プログラムを実行するときに
複数でマルチスレッドとして処理させたり、
単数でシングルスレッドとして処理できます。

例えばゲーム制作だと、
ゲームの中の計算をする処理と
ゲームのグラフィックを描画する処理があると思います。
Unityで普通に製作していると、シングルスレッドで
この２つの処理を同時に動かしているので、
計算の処理が複雑になったりすると重くなってしまいます。

そのため、計算部分は別のスレッドにやってもらおう！
みたいな使い方ができます。



詳しくは書かないですが、C#にはTaskというスレッド処理の機能が元々あります。
Unityではそのまま使うのが大変な機能でしたが、それを使いやすくしてくれたのがUniTaskに
なります。

```C#:@Sample06
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の処理はゲームオブジェクトが破壊されても動きます。
そのため破壊されたら処理を止めたい場合、自分でその処理を書かないといけないです。

今回は左クリックしたら自身のゲームオブジェクトを破壊し、処理を
止めるというコードを書きます。


```C#:@Sample07
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に取り組むのも良いかもしれない
ですね。

# おわりに
勉強会用の資料ということで、まとめてみました。
何かしら間違っている部分があったら申し訳ないです。
その時は教えていただけると助かります！