Taskじゃない非同期をTaskに変換したい（TaskCompletionSource）

#はじめに
Task(UniTask)は便利です。
特にこの昨今awaitで結果を待てるので手続き処理っぽく書きつつ非同期処理が書けます。

でも、 **Taskじゃない非同期処理はどうしましょう。**

例えばボタンのクリック。　ボタンのクリックはユーザーが気が向いたときにするものなので**非同期**です。

PlayFabやNCMBといったmBaasのAPI呼び出しなどは通信中画面が止まっては困るので**コールバック**で結果（もしくはエラー）を受け取るようになっています。　これもまた**非同期**です。


こういった**「非同期だけれど、Taskではない」**

をTaskに変換してくれる便利な奴がいます。　それが　**TaskCompletionSource(UniTaskCompletionSource)**です。

*※この記事では基本Task/TaskCompletionSourceを使いますが、適宜UniTask/UniTaskCompletionSourceに読みかえてください*

#基本形

```cs
public Task<int> GetIntAsync(){
    var tcs = new TaskCompletionSource<int>();



    return tcs.Task;
}
```

intを返すTask `Task<int>` に対応するのは `TaskCompletionSource<int>` ですし
floatを返すTask `Task<float>` に対応するのは `TaskCompletionSource<float>` です。

この返却したいTaskに対応した TaskCompletionSource を new して、 Task をreturn するのが基本形です。

しかし、このメソッドのTaskはいつまでたっても結果を返しません。　それはそう。 **誰も結果を返してないからです。**

#結果のセット

結果を返すにはこのTaskCompletionSourceのインスタンスに `TrySetResult` を呼ぶ必要があります。
（なお、キャンセルする場合は `TrySetCanceled()`を呼ぶ必要がありますが、それはまたの機会に・・・）

```cs
public Task<int> GetIntAsync(){
    var tcs = new TaskCompletionSource<int>();

    tcs.TrySetResult(32768);//結果のセット

    return tcs.Task;
}

public async Task RunAsync(){
    var num = await GetIntAsync(); 
    Debug.Log(i); //→ 32768 
}
```

これで、このメソッドはTaskの結果として32768を返すようになります。
さて。これは非同期でしょうか。**違いますね。** これでは `return Task.FromResult(32768);` と同じです。

しかし、ここで一つとても重要なことがあります。それはこの **TrySetResultは非同期で呼んでも構わない**ということです。

と言われても良くわからない方もいるでしょう。
例えば。　最初に例で出した **ボタンのクリック** を絡めてみます。

#例1 指定したボタンをクリックしたらintを返却するTask

まず、基本形を作ります

```csharp
public Task<int> ButtonClickAsync(){
    var tcs = new TaskCompletionSource<int>();

    return tcs.Task;
}
```

「指定したボタンを」とあるので、引数でボタンを受け取ります

```csharp
public Task<int> ButtonClickAsync(Button btn){
    var tcs = new TaskCompletionSource<int>();

    return tcs.Task;
}
```

ボタンがクリックされたら、結果を返却するようにします。　結果を返却するには `TaskCompletionSource` の `TrySetResult` を呼べば良いのでした。

```csharp
public Task<int> ButtonClickAsync(Button btn)
{
    var tcs = new TaskCompletionSource<int>();

    btn.onClick.AddListener(() => tcs.TrySetResult(65536));//ボタンクリックイベントのコールバックで結果セット
    
    return tcs.Task;
}
```

完成です。これで、ボタンをクリックするとこの　`tcs.Task` は 65536 を返却して終了するようになります。
簡単ですね。無事 **Taskじゃない非同期処理をTask化できました**

*※これだとbtnのonClickイベントが登録されっぱなしじゃないか！と思った貴方。大正解です。本当はちゃんと解除してあげてください。*

では、次はほんのちょっと応用です。 （今となってはObsoleteな)WWWクラスをTask化してみます

#例2 指定したURLからページ情報を取得するTask

これまた、基本形を作ります。　ページ情報なので Task は `Task<string>`  TaskCompletionSourceも `TaskCompletionSource<string>` にします。

```csharp
public Task<string> WWWAsync(){
    var tcs = new TaskCompletionSource<string>();

    return tcs.Task;
}
```

「指定したURLから」とあるので、引数でurlを受け取ります

```csharp
public Task<string> WWWAsync(string url){
    var tcs = new TaskCompletionSource<string>();

    return tcs.Task;
}
```

WWWクラスはコルーチンで結果を待つ仕組みなので、StartCoroutineで指定したurlからWWWでコンテンツを取得するようにします。

```csharp
public Task<string> WWWAsync(string url){
    var tcs = new TaskCompletionSource<string>();

    StartCoroutine(WWWGet(url));

    return tcs.Task;
}

private IEnumerator WWWGet(string url)
{
    var www = new WWW(url);
    yield return www;
    //wwww.text; //TODO ここにダウンロードしたコンテンツが入っている
}
```

結果を返却するには `TaskCompletionSource` の `TrySetResult` を呼べば良いのですが、コルーチンの結果は（基本的には）取得できないので、WWWGetの引数に `TaskCompletionSource<string>` を追加して、WWWGetの中で結果を入れてもらうように修正します。

```cs
public Task<string> WWWAsync(string url)
{
    var tcs = new TaskCompletionSource<string>();

    StartCoroutine(WWWGet(url,tcs));

    return tcs.Task;
}

private IEnumerator WWWGet(string url, TaskCompletionSource<string> tcs)
{
    var www = new WWW(url);
    yield return www;
    tcs.TrySetResult(www.text);
}
```

完成です。　コルーチンによる非同期もTaskにすることが出来ました。

#合体！
完全に蛇足ですが、上記二つの例を組み合わせると、「ボタンがクリックされるまで待ち、クリックされたら指定されたURLにアクセスしてコンテンツを取得する」Taskが作れます。

```csharp
    private async Task RunAsync()
    {
        var i = await ButtonClickAsync(_btn);//指定したボタンがクリックされるのを待つTask
        Debug.Log(i);

        var content = await WWWAsync("http://yahoo.co.jp");//指定したURLのコンテンツを取得するTask
        Debug.Log(content);
    }
```

んー。すっきりしてますね！

#最後に
今回は非常にシンプルな例を挙げましたが、使ってみるとっかかりにでもなれば幸いです。

Taskにしておくと結果が取得しやすいのはもちろんのこと、`Task.WhenAny`や`Task.WhenAll`を使って、複数あるうちのどれか一つのTaskが完了待ちや全てのTask完了待ちなど、ただの非同期処理では難しかった*非同期処理同士の連携*が簡単にできるようになったりするので（状況に応じて）Task化進めるのも良いと思います。


#補足
- 基本形として　`TaskCompletionSource` をローカル変数で作りましたが、メンバ変数でも構いません。　というか、その方が便利な場合も多々あります。　ただし、`TaskCompletionSource`の使いまわしはできません。　1回 SetResultしたらそれで終わりなので、また同様のTaskを呼ぶ場合は `TaskCompletionSource` を再インスタンス化する必要があります。
- ボタンクリックや、wwwの待ち等は実は UniRX.Async を使っていればこんなことせずに await が可能です。　(ボタンクリック待ちのTaskは `.OnClickAsync();` が用意されています）今回の内容はあくまでも `TaskCompletionSource` の使い方の簡単な説明なのであしからず。
- `TaskCompletionSource` には、**結果を返さないただのTask**がありません。　そのため別に返却する値が無い場合でも `TaskCompletionSource<object>` とし、 `TrySetResult(null);` とやることがよくあります。　が、 `UniTaskCompletionSource` には非Generics版が用意されているので、返却値が無い場合はそちらを使用しましょう。
- イベント周りはUniRX使った方が適切なケースも多いです。適宜使い分けましょう。

よい。Taskライフを。