Unity2017で始めるTask(async～await)

[C#6.0時代のUnity](http://qiita.com/divideby_zero/items/71a38acdbaa55e88e2d9) の続きとして、書く書く詐欺だったUnityでのTask(async~await)についてになります。
（さすがに[Unity2017.1が正式リリース](https://store.unity.com/download?ref=update) されたので・・。）

なお、UnityでTaskを使うにあたっての事前準備は[そちら](http://qiita.com/divideby_zero/items/71a38acdbaa55e88e2d9) を先に見てください

## Task
まず、前提知識としてTaskってなんぞや？　という事なんですが、まんま和訳した「仕事」ってのがしっくりくると思います。
やってほしい仕事＝タスク。
スレッドと混同されがちですが、スレッドは仕事をする側で、仕事そのものでは無いんじゃないかなーと（ざっくり）

そして、Task(async,await)を扱うにあたって

- Task.Delay
- Task.Run
- SynchronizedContext
- TaskCompletionSource

あたりを分かった気でいる必要があります。

しかし、先に言っておきたいんですが**Taskは難しい**です。
覚えることが多く、制約も多く、苦労して使えるようになっても、
「それコルーチン（UniRX）で出来るよね？」
「なんでそんなわざわざ面倒な方法で・・・？」
「なんだ。C#分かってますアピールか。」
と言われたりします。絶対。（偏見）

なので、Unity2017でTaskが使えるようになったからって、無理してTask使わなくてもいいと思っています（特に業務・複数人でUnity使っているような環境の方は）

Unity年表上

- Coroutine
- UniRX
- Task ← **NEW!!**

ってだけ、選択肢が増えただけ。それぐらいの立ち位置がまだいいんじゃないかなぁと個人的には思います。
（とは言え時代は動き出してしまったし、TaskにはTaskの便利な使い方があるので、難しいところ。ぐぬぬ。）

では。まずジャブから。

## Task.Delay

Task.Delayは指定ミリ秒(もしくは指定TimeSpan)だけ待ってから完了するTaskです。

例として

- 0~9の値を1秒間隔で表示したい。

という課題があるとします。

例えばこんな感じで

```csharp
using System;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;

public class DotNet46Test : MonoBehaviour 
{
    public Button btn;//InspectorからuGUIのButtonをセットしておく
    private void Start()
    {
        btn.onClick.AddListener(()=>Func1());
    }

    private void Func1()
    {
        LogOutput("---Start---");
        for (var i = 0; i < 10; ++i)
        {
            Task.Delay(1000);
            LogOutput($"Count:{i}");
        }
        LogOutput("---End---");
    }

    private void LogOutput(string message)
    {
        Debug.Log(DateTime.Now.ToString("yyyy/MM/dd hh:mm:ss") + "\t" + message);
    }
}
```

さて。これでボタンをクリックすると、ログはどのような出力になるかわかりますでしょうか。

これは

```text:出力
2017/07/18 05:26:37	---Start---
2017/07/18 05:26:37	Count:0
2017/07/18 05:26:37	Count:1
2017/07/18 05:26:37	Count:2
2017/07/18 05:26:37	Count:3
2017/07/18 05:26:37	Count:4
2017/07/18 05:26:37	Count:5
2017/07/18 05:26:37	Count:6
2017/07/18 05:26:37	Count:7
2017/07/18 05:26:37	Count:8
2017/07/18 05:26:37	Count:9
2017/07/18 05:26:37	---End---
```

見ての通り、Start→Count0～9→Endまで瞬にして出力されます。　**全然Delayしてない。**


と、いうのも、Taskはそもそも非同期に実行されるものなので、Task.Delayは正しく非同期的に1秒待っているんですが、非同期が故にその次の行のLog出力にそのまま進んでしまいます。

すなわち**Task.Delayの完了を待つ処理**が入っていないのでこうなってしまいます。
（スレッド自体を寝かせる`Thread.Sleep`とは意味・振る舞いが違う！）

### ContinueWith

「Taskの完了を待って、続きの処理を書きたい場合には`ContinueWith`を使うのがいいとばっちゃが言ってた。」

じゃぁ、こうかな？と

```csharp
    private void Start()
    {
//        btn.onClick.AddListener(()=>Func1());
        btn.onClick.AddListener(()=>Func2());
    }
    private void Func2()
    {
        LogOutput("---Start---");
        for (var i = 0; i < 10; ++i)
        {
            Task.Delay(1000).ContinueWith(t => LogOutput($"Count:{i}"));
        }
        LogOutput("---End---");
    }

```

結果はひどいもんです。

```text:出力
2017/07/18 05:28:38	---Start---
2017/07/18 05:28:38	---End---
2017/07/18 05:28:39	Count:10
2017/07/18 05:28:39	Count:10
2017/07/18 05:28:39	Count:10
2017/07/18 05:28:39	Count:10
2017/07/18 05:28:39	Count:10
2017/07/18 05:28:39	Count:10
2017/07/18 05:28:39	Count:10
2017/07/18 05:28:39	Count:10
2017/07/18 05:28:39	Count:10
2017/07/18 05:28:39	Count:10
```

あちゃー。これは

- Task.Delay(1000)　で1秒待って
- ContinueWith(後続処理)でログ出力。

が一塊のTaskとして、10連続で呼ばれるので、1秒に1回Logが出るという事にはならず、1秒待ってから。しかもラムダ式がfor文のiをキャプチャするので、全部`Count:10`になってしまいます。　**悪化しとるがな**

ちがう。違うんだ。
なんで今までUnityでコルーチン使えば

```csharp
    private IEnumerable CountIterator()
    {
        LogOutput("---Start---");
        for (var i = 0; i < 10; ++i)
        {
            yield return new WaitForSeconds(1);
            LogOutput($"Count:{i}");
        }
        LogOutput("---End---");
    }
```

で、`StartCoroutine(CountIterator());`するだけだったのに！Taskってなんなんだよ！

貴方は裏切られた気持ちでいっぱいになります（なりません）

## そんな貴方に async await

コルーチン方式で書けるんであれば、じつはasync～await方式に変えるのは簡単です。

**１．終了を待ちたいTaskに`await`を付ける。**

`await`を付けたTaskは完了待ち＋結果取り出しになります。（結果取り出しについては後述）

```csharp
        private void Func3()
        {
            LogOutput("---Start---");
            for (var i = 0; i < 10; ++i)
            {
                await Task.Delay(1000);
                LogOutput($"Count:{i}");
            }
            LogOutput("---End---");
        }
```

これで、Task.Delayの終了をそこで待つので、1秒間隔でCount:0～9が表示されそうです。
**まぁ、これじゃビルド通らないんですけど**

というのも、**awaitはasync付きのメソッド(またはラムダ式)の中にしか書けない**という制約があるんですね。
なので、
**２．`await`を付けたからには、`async` をメソッド(ラムダ式)に付ける**

```csharp
        private async void Func3()
        {
            LogOutput("---Start---");
            for (var i = 0; i < 10; ++i)
            {
                await Task.Delay(1000);
                LogOutput($"Count:{i}");
            }
            LogOutput("---End---");
        }
```

おや。簡単ですね。　これで完成！！？　いや、あともうちょっと。

**３. `async void` は良いこと無いので基本`async Task` に変える**

詳しいことはまたいつか書きますが、世の中には**async void 警察**がいて、軽い気持ちで`async void`を残しておくと**すごく叱責される**ので注意してください。

さておき、最終形としては

```csharp
    private void Start()
    {
        //btn.onClick.AddListener(()=>Func1());
        //btn.onClick.AddListener(()=>Func2());
        btn.onClick.AddListener(()=>Func3());
    }
    private async Task Func3()
    {
        LogOutput("---Start---");
        for (var i = 0; i < 10; ++i)
        {
            await Task.Delay(1000);
            LogOutput($"Count:{i}");
        }
        LogOutput("---End---");
    }
```

ボタンを押すと、

```text:出力
2017/07/18 05:30:37	---Start---
2017/07/18 05:30:38	Count:0
2017/07/18 05:30:39	Count:1
2017/07/18 05:30:40	Count:2
2017/07/18 05:30:41	Count:3
2017/07/18 05:30:42	Count:4
2017/07/18 05:30:43	Count:5
2017/07/18 05:30:44	Count:6
2017/07/18 05:30:45	Count:7
2017/07/18 05:30:46	Count:8
2017/07/18 05:30:47	Count:9
2017/07/18 05:30:47	---End---
```

ようやくCount0～9が1秒間隔で出力されました。

## これってどのスレッドが実行しているの・・？

さて。このFunc3は非同期処理っぽいですが、スレッド的にはどうなっているんでしょうか。

実のところ、メインスレッド以外で処理されているのは Task.Delay(1000)だけです。
1000ミリ秒を別のスレッドで休んでるだけで、全体的にはメインスレッドで処理されています。
LogOutput処理をちょっと改造して`Thread.CurrentThread.ManagedThreadId`も出力してみるとよくわかります
(ついでにTask.DelayにContinueWithを使った後続処理でもLogを出力してみました。)

```csharp
using System;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;

public class DotNet46Test : MonoBehaviour {
    public Button btn;//InspectorからuGUIのButtonをセットしておく
    private void Start()
    {
        LogOutput("MainThreadID");
        btn.onClick.AddListener(() => Func3());
    }
    private async Task Func3()
    {
        LogOutput("---Start---");
        for (var i = 0; i < 10; ++i)
        {
            await Task.Delay(1000).ContinueWith(t=>LogOutput($"ContinueWith:{i}"));
            LogOutput($"Count:{i}");
        }
        LogOutput("---End---");
    }

    private void LogOutput(string message)
    {
        Debug.Log(DateTime.Now.ToString("yyyy/MM/dd hh:mm:ss") + "\t" + message + ":" + Thread.CurrentThread.ManagedThreadId);
    }
}
```

```text:出力
2017/07/18 10:58:35	MainThreadID:1
2017/07/18 10:58:38	---Start---:1
2017/07/18 10:58:39	ContinueWith:0:83
2017/07/18 10:58:40	Count:0:1
2017/07/18 10:58:41	ContinueWith:1:82
2017/07/18 10:58:41	Count:1:1
・
・
・
2017/07/18 10:58:49	ContinueWith:9:43
2017/07/18 10:58:49	Count:9:1
2017/07/18 10:58:49	---End---:1
```

Count:0～9はすべてThreadID:1(メインスレッドIDと同じ）で、
逆にContinueWithでの後続処理のThreadIDはすべて異なるという結果に。

メインスレッドで動いているということはどういうことかというと、**重い処理を走らせたら画面が止まる**ということです。　async付けたら非同期、メインスレッド外だと思っている人も時々いるようですが、そうではないです。
でも・・え。止まっちゃダメじゃん。　何のためのTask,非同期なのさ。

## Task.Run

では、**明示的に何か処理をメインスレッド以外で動かしたい**となった場合にどうすればよいかというと、`Task.Run`を使うと別スレッドで（しかもスレッドプールというイカした仕組みで）動くようになります。

テストのために重い処理を持ってきました。
大分昔に書いた記事ですが、
[Unityでブラー画像動的生成](http://qiita.com/divideby_zero/items/4c02177a56f7d500d4c0)
の、アルファチャンネルをブラー化したTexture2Dを作成する処理です。

```csharp
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;

public static class BuildTexture
{
    private static Dictionary<Texture2D, Texture2D> _textureCache = new Dictionary<Texture2D, Texture2D>();

    public static Texture2D CreateBlurTexture(Texture2D tex, float sig, bool isCache = true)
    {
        if (isCache && _textureCache.ContainsKey(tex)) return _textureCache[tex];

        int W = tex.width;
        int H = tex.height;
        int Wm = (int)(Mathf.Ceil(3.0f * sig) * 2 + 1);
        int Rm = (Wm - 1) / 2;

        //フィルタ
        float[] msk = new float[Wm];

        sig = 2 * sig * sig;
        float div = Mathf.Sqrt(sig * Mathf.PI);

        //フィルタの作成
        for (int x = 0; x < Wm; x++)
        {
            int p = (x - Rm) * (x - Rm);
            msk[x] = Mathf.Exp(-p / sig) / div;
        }

        var src = tex.GetPixels(0).Select(x => x.a).ToArray();
        var tmp = new float[src.Length];
        var dst = new Color[src.Length];

        //垂直方向
        for (int x = 0; x < W; x++)
        {
            for (int y = 0; y < H; y++)
            {
                float sum = 0;
                for (int i = 0; i < Wm; i++)
                {
                    int p = y + i - Rm;
                    if (p < 0 || p >= H) continue;
                    sum += msk[i] * src[x + p * W];
                }
                tmp[x + y * W] = sum;
            }
        }
        //水平方向
        for (int x = 0; x < W; x++)
        {
            for (int y = 0; y < H; y++)
            {
                float sum = 0;
                for (int i = 0; i < Wm; i++)
                {
                    int p = x + i - Rm;
                    if (p < 0 || p >= W) continue;
                    sum += msk[i] * tmp[p + y * W];
                }
                dst[x + y * W] = new Color(1, 1, 1, sum);
            }
        }

        var createTexture = new Texture2D(W, H);
        createTexture.SetPixels(dst);
        createTexture.Apply();

        if (isCache)
        {
            _textureCache.Add(tex, createTexture);
        }

        return createTexture;
    }

    public static void Release()
    {
        _textureCache.Clear();
    }
}
```

そして、このブラー処理を呼び出す処理も用意しました。

```csharp
using System;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;

public class BlurTest : MonoBehaviour
{
    public Button button;
    public SpriteRenderer spriteRenderer;

	void Start ()
    {
		button?.onClick.AddListener(() => {
		    var btex = BuildTexture.CreateBlurTexture(spriteRenderer.sprite.texture, 32.0f, false);
		    spriteRenderer.sprite = Sprite.Create(btex, spriteRenderer.sprite.rect, new Vector2(0.5f, 0.5f));
		});
    }
}
```

ボタンをクリックすると、同じ場所に重ねてある片方のSpriteのTextureをアルファチャンネルをブラー化したテクスチャに変更するというものです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/37184/b188a90a-0368-7eff-faa1-da7181d888be.png)
　　↓
![image.png](https://qiita-image-store.s3.amazonaws.com/0/37184/1c90f819-6bba-421a-cef3-34143e1ad21e.png)
（オーラをまとった！！）

しかしながら、これは全てメインスレッドで動いているので、僕のPC環境だとボタンを押してから２～３秒**UnityEditorそのもの**が固まります。

では。この処理を非同期(別スレッドで並列処理）できるように変更します。

このブラー化する処理で明らかに重そうなのはこの、**恐怖の3重ループ*2**で全てのピクセルの近傍ピクセルを走査しまくっているところです。

```csharp:重いところ
        //垂直方向
        for (int x = 0; x < W; x++)
        {
            for (int y = 0; y < H; y++)
            {
                float sum = 0;
                for (int i = 0; i < Wm; i++)
                {
                    int p = y + i - Rm;
                    if (p < 0 || p >= H) continue;
                    sum += msk[i] * src[x + p * W];
                }
                tmp[x + y * W] = sum;
            }
        }
        //水平方向
        for (int x = 0; x < W; x++)
        {
            for (int y = 0; y < H; y++)
            {
                float sum = 0;
                for (int i = 0; i < Wm; i++)
                {
                    int p = x + i - Rm;
                    if (p < 0 || p >= W) continue;
                    sum += msk[i] * tmp[p + y * W];
                }
                dst[x + y * W] = new Color(1, 1, 1, sum);
            }
        }
```

なので、この処理を`Task.Run`で別スレッド処理に逃がしてあげます。

```csharp
        Task.Run(() => {
            //垂直方向
            //<省略>
            //水平方向
            //<省略>
        });
```

このTaskの終了を待たずにdstを使う後続処理を走らせるわけにはいかないので、`await`キーワードを追加します。

```csharp
        await Task.Run(() => {
            //垂直方向
            //<省略>
            //水平方向
            //<省略>
        });
```

`await`を使うからには、`async`をメソッドに付けます（2回目）

```csharp
    public static async Texture2D CreateBlurTexture(Texture2D tex, float sig, bool isCache = true)
```

`async`メソッドは戻り値がTaskにする必要があるので、`Task`に。　しかもこれはもともと`Texture2D`を返すメソッドなので、
`Task<Texture2D>`に変更します(あと、非同期処理にするので、メソッド名に`Async`等を付けるのが一般的です。）

```csharp
    public static async Task<Texture2D> CreateBlurTextureAsync(Texture2D tex, float sig, bool isCache = true)
```

これで、ブラー処理自体のTask化が完了しました。
もちろん、呼び出し元にも変更が必要です（メソッド名も変えちゃったし）

```csharp:元
    void Start ()
    {
		button?.onClick.AddListener(() => {
		    var btex = BuildTexture.CreateBlurTexture(spriteRenderer.sprite.texture, 32.0f, false);
		    spriteRenderer.sprite = Sprite.Create(btex, spriteRenderer.sprite.rect, new Vector2(0.5f, 0.5f));
		});
    }
```

まず、メソッド名が変わってますし、非同期処理なので、`await`で待つようにしてあげる必要があります。

```csharp:await追加、メソッド名変更
    void Start ()
    {
		button?.onClick.AddListener(() => {
		    var btex = await BuildTexture.CreateBlurTextureAsync(spriteRenderer.sprite.texture, 32.0f, false);
		    spriteRenderer.sprite = Sprite.Create(btex, spriteRenderer.sprite.rect, new Vector2(0.5f, 0.5f));
		});
    }
```

そして`await`を使うには`async`キーワードが必要になります（3回目）
しかし、これはラムダ式・・・。　どこにasyncを・・・。　となりがちですが、ラムダ式への引数の前、今回は引数無しなので**()の手前**でOKです。

```csharp:async追加
    void Start ()
    {
		button?.onClick.AddListener(async() => {
		    var btex = await BuildTexture.CreateBlurTextureAsync(spriteRenderer.sprite.texture, 32.0f, false);
		    spriteRenderer.sprite = Sprite.Create(btex, spriteRenderer.sprite.rect, new Vector2(0.5f, 0.5f));
		});
    }
```

これで、呼び出し元も修正完了です。
なんとこれだけの修正でTask化され、無事UnityEditorが固まらなくなるのです！！

「完了？あれ？　でも`CreateBlurTextureAsync`の戻り値は`Texture2D`から`Task<Texture2D>`に変更してたよね？変数`btex`には`Task<Texture2D>`が入っているのでは？」

という当然の疑問が残りますが、これで問題はありません。

これが`await`の機能。　終了を待つと同時に、**Taskの結果を取り出してくれる**のです。　（伏線回収)
本来、Task<T>から結果(T)を取得するにはTaskのResultプロパティを参照する必要があるのですが、awaitはそのResultを勝手に取り出してくれるのでした。　超便利。

どうでしょう。Task化。そんな難しくないですね！？（しかし、本当の沼はこれからなのでした）

## SynchronizedContext
さて。先ほどのCreateBlurTextureのTask化（Async化）ですが、**あえてちょっとミスリード**させました。

>このブラー化する処理で明らかに重そうなのはこの、**恐怖の3重ループ*2**で全てのピクセルの近傍ピクセルを走査しまくっているところです。
...
なので、この処理を`Task.Run`で別スレッド処理に逃がしてあげます。

はい。ここですね。
さも、**「ボトルネックなのでTask.Runで別スレッドに逃がしましたよ」**的な感じで書きましたが、**実際にはそこぐらいしかTask.Runで逃がせられなかった**のです。

と、いうのも、`Task.Run`や`Task.Delay`、それらを`ContinueWith`した後続処理なんかは、別スレッドで動いています。
そして、**Unity固有の処理はメインスレッド以外からアクセスするとエラーになる**という制約があります。

例えば

```csharp
Task.Run(()=>Debug.Log(transform.position));
```

たったこの1行でアウトです。ログには

![image.png](https://qiita-image-store.s3.amazonaws.com/0/37184/a21d13b9-590f-13fd-881f-f8abc8105ff0.png)
`get_transform can only be called from the main thread.`

こんな感じで「transformはメインスレッドからしか呼ばせないよ！！」と言われちゃいます。transform取るくらい・・・って思わなくもないんですが。

同じように例えばTexture2Dの横幅(Width)をTask.Runの中で取得したりすると同じく
![image.png](https://qiita-image-store.s3.amazonaws.com/0/37184/b466ae54-50b7-73dc-f956-bebfa4a3fda1.png)
**超怒られます。**

なので、今回はUnityに関係ない、配列操作部分だけを`Task.Run`で非同期化したわけです。

でも、なんとかして`Task.Run`（や`Task.Delay`の後続タスク等）でもUnityの処理が呼びたい！！

そんな時に使うのが`SynchronizedContext`(同期コンテキスト)です。

方法は**２つ**

### 方法１.メインスレッドで`SynchronizationContext.Current`を取得し、`Post`を使ってUnityの処理を呼ぶ

```csharp:方法1例
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class ContextTest : MonoBehaviour
{
    void Start()
    {
        var context = SynchronizationContext.Current;
        Task.Run(async () => {
            await Task.Delay(1000);
            //Debug.Log(transform.position);    //ここだと別スレッドなのでエラー
            context.Post(state => Debug.Log(transform.position), null);
        });
    }
}
```

これはエラーになりません。
javaの`runOnUiThread`的な使い方ですね。
なお間違っても`SynchronizationContext.Current`をTask.Runの中で取得したりしないように。　多分nullが入ってます。
そして、Postした処理は即時実行されるわけでもなく、さらにPost終了を待機できるわけでもないので要注意です。　Postなので、通知専用。　非同期処理の進捗表示ぐらいにしか使えなさそう。

###方法２.メインスレッドで`SynchronizationContext.Current`を取得し、`await`によって戻るContextを先に`SynchronizationContext.SetSynchronizationContext`でセットしておく

```csharp:方法2例
    void Start()
    {
        var context = SynchronizationContext.Current;
        Task.Run(async () => {
            SynchronizationContext.SetSynchronizationContext(context);
            //Debug.Log(transform.position);    //ここだとawait前なのでエラー
            await Task.Delay(1000);
            Debug.Log(transform.position);      //ここだとOK
        });
    }
```

`Task.Run`や`Task.Delay`なんかは別スレッドで処理されているというのは既に何度も出てきてるんですが、`await`で別スレッドの結果を待って何事もなかったようにスレッドがメインスレッド（というか、`await`前のスレッド)に戻るのはこの`await`がTaskを呼び出した瞬間の`SynchronizedContext`をキャプチャしておいて、終了後、キャプチャしておいた`SynchronizedContext`のPostを使って後続処理を走るように上手くやっているからとかなんとか・・・（あってるのかな・・・？）

なので先に自分で`SetSynchronizationContext`を呼んでセットしてあげれば、await後にはUnityの処理を呼んでもよいという寸法。うーん、ややこしやややこしや。

どちらにせよ、Task.Runの中でUnity絡みの操作を行うのは面倒ですが・・・

## そもそもそもそも
その`Task.Run`要りますか？
今回の例ではもちろん必要ないですが、世の中には**不要なTask.Run**で溢れかえっている可能性が高いです。
無駄な`Task.Run`によるラップが無ければ、`SynchronizedContext`を使ったり、特別意識する必要も（そんなに）ないんじゃないかなーと。

## もっと書きたいけど、書ききれなかったもの
- TaskCompletionSource
- CancellationTokenSource(CancellationToken)

いつか書きます。
あと、ボタンのクリックを待つTaskを作る拡張メソッドだったり、それを組み合わせて
「10秒経過かその間にボタンがクリックされたら次の処理」
みたいなコルーチンでやると意外と面倒というか、状態を保持する必要がある処理があっさり書けたりするので、実例交えて紹介したかったんですが。

すでにそこそこな長さになってきたので、それらも含めまたおいおい・・・。

## 良いTaskライフを