LoginSignup
274
256

More than 3 years have passed since last update.

C# Taskの待ちかた集

Last updated at Posted at 2017-04-20

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

Taskの使い方とかはこっち ⇒ C# 並行・並列プログラミング パターン集

await する

普通のパターン。

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 するタイミングをずらすことで、パフォーマンスを改善できるかもしれない。

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 する

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

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

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

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: 非同期プログラミングのベスト プラクティス の「すべて非同期にする」を参照。

WhenAll する

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

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 すればよいときに便利。

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);

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

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)など、細かい制御が可能。

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 を使う。

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 ってのもある。

他?

274
256
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
274
256