C# Taskの待ちかた集

  • 34
    いいね
  • 2
    コメント

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

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

await する

普通のパターン。

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

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

あとで await する

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

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

    MessageBox.Show("HTMLを取得しています");
    textBox.Text = await gethtmltask;
}

メッセージボックスを表示している裏でHTTP通信するので、普通のパターンよりも体感待ち時間の短縮が期待できる。

WhenAll, WhenAny する

複数の Task を await するのに便利。

WebClient wc1 = new WebClient();
WebClient wc2 = new WebClient();
Task<string> dlRotobstxtTask = wc1.DownloadStringTaskAsync("http://example.jp/robots.txt");
Task<byte[]> dlExeTask = wc2.DownloadDataTaskAsync("http://example.jp/malware.exe");

// どれかの Task が終わるまで待つ Task
await Task.WhenAny(dlRotobstxtTask, dlExeTask);

// すべての Task が終わるまで待つ Task
await Task.WhenAll(dlRotobstxtTask, dlExeTask);

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() を引数に渡している)

Wait(), Result する

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

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

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

WebClient wc1 = new WebClient();
WebClient wc2 = new WebClient();

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

// Task が終わるまでスレッドをブロックし、結果を取得する
byte[] exe = dlExeTask.Result;

// どれかの Task が終わるまでスレッドをブロックする
Task.WaitAny(dlRotobstxtTask, dlExeTask);

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

※GUIアプリやWebアプリでWaitするのは危険。詳細は Async/Await: 非同期プログラミングのベスト プラクティス の「すべて非同期にする」を参照。

他?