C# Taskの待ちかた集

Taskの完了を待ったり結果を取得したりする方法がいろいろあるので整理。

Taskの使い方とかはこっち ⇒ [C# 並行・並列プログラミング パターン集](http://qiita.com/takutoy/items/c384fcb439d345a9a0d3#%E9%9D%9E%E5%90%8C%E6%9C%9Ftask%E3%82%92%E4%B8%A6%E8%A1%8C%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B)

# await する
普通のパターン。

```csharp
HttpClient hc = new HttpClient();
private async void button_Click(object sender, RoutedEventArgs e)
{
    string html = await hc.GetStringAsync("http://www.example.jp/");

    MessageBox.Show("HTMLの取得が完了しました");
    textBox.Text = html;
}
```

# あとで await する
await するタイミングをずらすことで、パフォーマンスを改善できるかもしれない。

```csharp
HttpClient hc = new HttpClient();
private async void button_Click(object sender, RoutedEventArgs e)
{
    Task<string> gethtmltask = hc.GetStringAsync("http://www.example.jp/kussoosoi.php");

    MessageBox.Show("HTMLを取得しています");
    textBox.Text = await gethtmltask;
}
```
メッセージボックスを表示している裏でHTTP通信するので、普通のパターンよりも体感待ち時間の短縮が期待できる。

# Wait, Result する
非同期メソッドを同期メソッドのように使用することもできる。

```csharp
HttpClient hc = new HttpClient();
string html = hc.GetStringAsync("http://example.jp/").Result;
```

Result の仲間に Wait() や Task.WaitAll(), Task.WaitAny() がいる。

```csharp
HttpClient hc = new HttpClient();

Task<string> t1 = hc.GetStringAsync("https://www.microsoft.com/");
Task<string> t2 = hc.GetStringAsync("https://www.bing.com/");

// Task が終わるまでスレッドをブロックする
t1.Wait();

// Task が終わるまでスレッドをブロックし、結果を取得する
string binghtml = t2.Result;

// どれかの Task が終わるまでスレッドをブロックする
int completedTaskIndex = Task.WaitAny(t1, t2);
// -> 0 (if www.microsoft.com のほうが速い) or
//    1 (if www.bing.com のほうが速い) 

// どれかの Task が終わるまでスレッドをブロックする(タイムアウト付き)
int completedTaskIndex2 = Task.WaitAny(new[] { t1, t2 }, 50);
// -> 0 (if www.microsoft.com のほうが速い) or
//    1 (if www.bing.com のほうが速い) or
//   -1 (if 両方とも50ミリ秒以内に応答がない)

// すべての Task が終わるまでスレッドをブロックする
Task.WaitAll(t1, t2);

// すべての Task が終わるまでスレッドをブロックする(タイムアウト付き)
bool allTasksCompleted = Task.WaitAll(new[] { t1, t2 }, 50);
// ->  true (両方とも50ミリ秒以内に応答がある) or
//    false (どちらか、または両方とも50ミリ秒以内に応答がない)
```



※GUIアプリやWebアプリでWaitするのは危険
詳細は [Async/Await: 非同期プログラミングのベスト プラクティス](https://msdn.microsoft.com/ja-jp/magazine/jj991977.aspx) の「すべて非同期にする」を参照。

# WhenAll する

複数の Task をまとめて await するのに便利。

```csharp
HttpClient hc = new HttpClient();

Task<string> t1 = hc.GetStringAsync("https://www.microsoft.com/");
Task<string> t2 = hc.GetStringAsync("https://www.bing.com/");

string[] htmls = await Task.WhenAll(t1, t2);
// -> htmls[0]:www.microsoft.com の HTML
//    htmls[1]:www.bing.com の HTML
```

# WhenAny する

複数の Task のうち、どれか1つだけ await すればよいときに便利。

```csharp
HttpClient hc = new HttpClient();

Task<string> t1 = hc.GetStringAsync("https://www.microsoft.com/");
Task<string> t2 = hc.GetStringAsync("https://www.bing.com/");

Task<string> completedTask = await Task.WhenAny(t1, t2);
```

応用して、残りのタスクをキャンセルすることも。


```csharp
HttpClient hc = new HttpClient();
CancellationTokenSource cts = new CancellationTokenSource();

// ※GetStringAsync() には CancellationToken を受け付けるオーバーロードがなかったので仕方なく GetAsync()
Task<HttpResponseMessage> t1 = hc.GetAsync("https://www.microsoft.com/", cts.Token);
Task<HttpResponseMessage> t2 = hc.GetAsync("https://www.bing.com/", cts.Token);

// 完了したタスクを取得する
Task<HttpResponseMessage> completedTask = await Task.WhenAny(t1, t2);

// ほかのタスクはすべてキャンセルする
cts.Cancel();

// 完了したタスクの結果を取得する
HttpResponseMessage msg = await completedTask;
string html = await msg.Content.ReadAsStringAsync();
```

# ContinueWith する
ContinueWith を使うと、次のタスクを実行する条件(TaskContinuationOptions や TaskScheduler)など、細かい制御が可能。

```csharp
private void button1_Click(object sender, RoutedEventArgs e)
{
    WebClient wc = new WebClient();
    wc.DownloadDataTaskAsync("https://example.jp/kusodekagazou.png")
        .ContinueWith(task =>
        {
            // ダウンロードしたバイト列を画像にデコードする

            // task.Result で前のタスクの結果を参照できる
            using (var mem = new MemoryStream(task.Result))
            {
                return BitmapFrame.Create(mem, 
                    BitmapCreateOptions.None, 
                    BitmapCacheOption.OnLoad);
            }
        })
        .ContinueWith(task =>
        {
            // デコードした画像を UI に表示する
            image.Source = task.Result;
        }, TaskScheduler.FromCurrentSynchronizationContext());
}
```
なお上記の例では、画像のデコードは Worker スレッドで実行され、画像の表示は UI スレッドで実行される。(ようにするために TaskScheduler.FromCurrentSynchronizationContext() を引数に渡している)

# IsCompleted をチェックする

タスクが終わってるか確認したいだけなら IsCompleted を使う。

```csharp
HttpClient hc = new HttpClient();
Task<string> task = hc.GetStringAsync("https://www.microsoft.com/");

// タスクが終わっている（成功、失敗、キャンセル）かチェック
if (!task.IsCompleted)
{
    // www.microsoft.com の応答が超速なら、このメッセージは表示されない！
    Console.WriteLine("ちょっとまってね");
}

var html = await task;
Console.WriteLine(html);
```

IsCompleted に似て IsCompletedSuccessfully というプロパティもあるが、
IsCompletedSuccessfully はタスクが成功した場合のみ true になる。
あと IsFaulted や IsCanceled ってのもある。

# 他？