【Unity】非同期処理を理解する〜コルーチン編〜

# コルーチンとは

- 非同期処理できる。
- 関数を任意のタイミングで中断・再開できる機能。
- 複数の処理を**疑似並列**できる。（完全な並列ではない）
- Unity のメインスレッドで処理する。
- 結果は返せない。
- GameObject と処理が紐づく。
- IEnumerator 型を戻り値とした関数を定義する。
- コルーチンを実行したいときは ```StartCoroutine(string コルーチン名)``` または ```StartCoroutine(IEnumerator型の変数)```
- コルーチンを停止（中断）したいときは ```StopCoroutine(string コルーチン名)``` または ```StopCoroutine(IEnumerator型の変数)```

# コルーチンの主要な機能一覧

| 機能 | 詳細 |
| :--- | :-- |
| yield return null | 1フレーム分処理を中断、次のフレームで続きの行を処理 |
| yield break | コルーチンを途中で終了、再開はできない |
| yield return new WaitForSeconds(float seconds) | 指定した seconds 秒、コルーチンを中断する |
| yield return new WaitUntil(Func predicate) | predicate の関数の返り値が **true** になったら処理を再開する |
yield return new WaitWhile(Func predicate) | predicate の関数の返り値が **false** になったら処理を再開する |
| yield return StartCoroutine() | 指定したコルーチンを実行、完了するまで後続処理は行わない |
| StopAllCoroutines() | コルーチンを全て止める |


## yield return null

```yield return null``` は、1フレーム分処理を中断して、次のフレームで続きの行を処理します。

```c#
using System.Collections;
using UnityEngine;

public class Test : MonoBehaviour
{
    void Start()
    {
        IEnumerator sample1 = Sample1();
        IEnumerator sample2 = Sample2();
        StartCoroutine(sample1);
        StartCoroutine(sample2);
    }

    IEnumerator Sample1()
    {
        Debug.Log("Sample1 Start.");
        yield return null;            // Sample1()の処理は1フレーム待機
        Debug.Log("Sample1 End.");
    }

    IEnumerator Sample2()
    {
        Debug.Log("Sample2 Start.");
        yield return null;            // Sample2()の処理は1フレーム待機
        Debug.Log("Sample2 End.");
    }
}
```

だから、結果は下の通りで、
1. Sample1 Start.
2. Sample2 Start.
3. Sample1 End.
4. Sample2 End.
の順で実行される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233011/810c0a2f-a7ce-a3a9-191b-05e374d82e13.png)

## yield break

```yield break``` は、コルーチンを途中で終了します。
再開はできないです。

```c#
using System.Collections;
using UnityEngine;

public class Test : MonoBehaviour
{
    void Start()
    {
        IEnumerator sample1 = Sample1();
        IEnumerator sample2 = Sample2();
        StartCoroutine(sample1);
        StartCoroutine(sample2);
    }

    IEnumerator Sample1()
    {
        Debug.Log("Sample1 Start.");
        yield break;                  // Sample1() の処理はここで止める
        Debug.Log("Sample1 End.");
    }

    IEnumerator Sample2()
    {
        Debug.Log("Sample2 Start.");
        yield break;                  // Sample2() の処理をここで止める
        Debug.Log("Sample2 End.");
    }
}
```

コルーチンを途中で終了したので、`Sample1 End.` と `Sample2 End.` は出力されません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233011/e38e0d1a-ac0d-9e73-546b-ffd0eb48224b.png)

## yield return new WaitForSeconds(float seconds)

```yield return new WaitForSeconds(seconds)``` は、指定した秒数の間、コルーチンの実行を待ちます。

```c#
using System.Collections;
using UnityEngine;

public class Test : MonoBehaviour
{
    void Start()
    {
        IEnumerator sample1 = Sample1();
        IEnumerator sample2 = Sample2();
        StartCoroutine(sample1);
        StartCoroutine(sample2);
    }

    IEnumerator Sample1()
    {
        Debug.Log("Sample1 Start.");
        yield return null;            // Sample1()の処理は1フレーム待機
        Debug.Log("Sample1 End.");
    }

    IEnumerator Sample2()
    {
        Debug.Log("Sample2 Start.");
        yield return new WaitForSeconds (1.0f); // Sample2()の処理は1秒待機
        Debug.Log("Sample2 End.");
    }
}
```

`Sample2 End.` が、他より1秒経過してから出力されるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233011/9476d4ec-8239-ba33-d74a-27392f1f5988.png)


## yield return new WaitUntil(Func<bool> predicate)

```yield return new WaitUntil(Func<bool> predicate)``` は、predicate で指定した関数が true を返したときに再開します。

```c#
using System.Collections;
using UnityEngine;

public class Test : MonoBehaviour
{
    bool flg = false;
    void Start()
    {
        IEnumerator sample1 = Sample1();
        IEnumerator sample2 = Sample2();
        StartCoroutine(sample1);
        StartCoroutine(sample2);
    }

    IEnumerator Sample1()
    {
        Debug.Log("Sample1 Start.");
        yield return new WaitUntil(() => flg == true); // flg が true になるまで処理が止まる
        Debug.Log("Sample1 End.");
    }

    IEnumerator Sample2()
    {
        Debug.Log("Sample2 Start.");
        yield return null;
        Debug.Log("Sample2 End.");
        flg = true; // Sample2()の最後に flg を true にする
    }
}
```

flg が true になるまで、 Sample1 End. が出力されないため、下記のような結果になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233011/5c0c6a09-6f4c-1469-ce67-d87f709d4497.png)

## yield return new WaitWhile(Func<bool> predicate)

```yield return new WaitWhile(Func<bool> predicate)``` は、predicate で指定した関数が false を返したときに再開します。
```WaitUntil(Func<bool> predicate)``` の逆バージョン。
使い方は上と一緒なので省略。

## yield return StartCoroutine()

```yield return StartCoroutine()``` は、指定したコルーチンを実行し、完了するまで処理を中断します。

```c#
using System.Collections;
using UnityEngine;

public class Test : MonoBehaviour
{
    bool flg = false;
    void Start()
    {
        IEnumerator sample1 = Sample1();
        StartCoroutine(sample1);
    }

    IEnumerator Sample1()
    {
        Debug.Log("Sample1 Start.");
        IEnumerator sample2 = Sample2();
        yield return StartCoroutine(sample2); // Sample2() を実行が終わるまで待機
        Debug.Log("Sample1 End.");
    }

    IEnumerator Sample2()
    {
        Debug.Log("Sample2 Start.");
        yield return null;           // Sample2()の処理は1秒待機
        Debug.Log("Sample2 End.");
    }
}

```

結果は下のような感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233011/39f1cb4c-759f-2401-d49c-037a26dd5296.png)


## StopAllCoroutines()

名前の通り、コルーチンの全てを止めます。
Behavior 上で実行されている全てを止めます。

```c#
using System.Collections;
using UnityEngine;

public class Test : MonoBehaviour
{
    void Start()
    {
        IEnumerator sample1 = Sample1();
        IEnumerator sample2 = Sample2();
        StartCoroutine(sample1);
        StartCoroutine(sample2);
        StopAllCoroutines(); // 全てのコルーチンを止める
    }

    IEnumerator Sample1()
    {
        Debug.Log("Sample1 Start.");
        yield return null;
        Debug.Log("Sample1 End.");
    }

    IEnumerator Sample2()
    {
        Debug.Log("Sample2 Start.");
        yield return null;
        Debug.Log("Sample2 End.");
    }
}
```

コルーチンを途中で終了したので、`Sample1 End.` と `Sample2 End.` は出力されません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233011/c3a1909e-879a-5715-06a0-37c4e1522579.png)

# 終わりに

Unity 初心者なので、間違いがあったら教えてくれるとありがたいです。
今まで雰囲気で非同期処理を書いていたので、しっかり勉強するために記事を書くことにしました。
いまのところ、async/await編、UniRx編を書く予定です。
挫折したらごめんなさい。

# 参考文献

[コルーチン - Unity マニュアル](https://docs.unity3d.com/jp/540/Manual/Coroutines.html)
[【C#/Unity】コルーチン(Coroutine)とは何なのか](https://doruby.jp/users/ino/entries/%E3%80%90C--Unity%E3%80%91%E3%82%B3%E3%83%AB%E3%83%BC%E3%83%81%E3%83%B3(Coroutine)%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%AA%E3%81%AE%E3%81%8B)
[Unityのコルーチン機能を使う](https://blog.cfm-art.net/archives/419)