LoginSignup
11
13

More than 3 years have passed since last update.

【C#】【Task】Taskを使って並行処理(並列処理?)をする・UIを固めずに重い処理をする

Last updated at Posted at 2019-04-10

目次
https://qiita.com/tera1707/items/4fda73d86eded283ec4f

やりたいこと

Taskを使って、重くて時間がかかる処理があっても、UIを固めずに済ませたい。

概要

  • "Task"を使う。
  • Taskは、スレッドプールを簡単に使えるようにしたもの(参考)。
  • Taskは、.NET Framework 4 以降で追加された。
  • .NET Framework 4 以前は、Threadクラスを使ってスレッド、スレッドプールを使ってマルチスレッドを行っていたが、4以降はTaskを使うべき。
  • 並列処理をするには、ほかにParallelクラスやParallelEnumerableクラスというのもあるらしい。(未検証)

Taskについて

こちらで、非常にわかりやすい説明をして頂いている。
ので、詳しい内容はそちらを参考として、理解を確かめる際に作ったサンプルを備忘録代わりに挙げておく。

基本の形

Task.Run()で、処理をスタートしつつ、Taskの戻り値を受け、それをTask.WhenAllで使って処理が終わるまで待つ。

// 基本
private async void Button_Click(object sender, RoutedEventArgs e)
{
    var task1 = Task.Run(() => 
    {
        Thread.Sleep(3000);
        Debug.WriteLine("task1 完了");
    });

    var task2 = Task.Run(() =>
    {
        Thread.Sleep(1000);
        Debug.WriteLine("task2 完了");
    });

    var task3 = Task.Run(() =>
    {
        Thread.Sleep(2000);
        Debug.WriteLine("task3 完了");
    });

    await Task.WhenAll(task1, task2, task3);
    Debug.WriteLine("task1,2,3 完了");
}

実験

どうなるのか?と思って試したコード。
Task.Run()で開始したタスクが、Task.WhenAll()の前に終わっていたらどうなる?
⇒結果、Task.WhenAll()を即通過した。

// 重い処理の代わりのメソッド
private void LongWaitingMethod(int millisec)
{
    Thread.Sleep(millisec);
    Debug.WriteLine("LongWaitingMethod " + millisec + " 完了");
}

// Task.WhenAllの前に、すでに全部のtaskが終わっている
private async void Button_Click_2(object sender, RoutedEventArgs e)
{
    var task1 = Task.Run(() => LongWaitingMethod(3000)); // task1 開始
    var task2 = Task.Run(() => LongWaitingMethod(1000)); // task2 開始
    var task3 = Task.Run(() => LongWaitingMethod(2000)); // task3 開始

    // 5秒ここで待つ(ブロック)
    LongWaitingMethod(5000);
    Debug.WriteLine("5秒待ち 終わり");

    // task1-3はすでに完了しているので、ここは即抜ける
    await Task.WhenAll(task1, task2, task3);
    Debug.WriteLine("task1,2,3 完了");
}

戻り値を返すTask

Taskの処理から、戻り値を受け取ることができる。
WhenAll()に渡したTaskすべてから受け取れる。

// 重い処理の代わりのメソッド(文字列を戻り値で返す)
private string LongWaitingMethodReturnString(int millisec)
{
    Thread.Sleep(millisec);
    Debug.WriteLine("LongWaitingMethod " + millisec + " 完了");
    return "LongWaitingMethod " + millisec + " 完了";
}

// 戻り値ありのTask(全部同じ型の戻り値)
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
    var task1 = Task<string>.Run(() => LongWaitingMethodReturnString(3000)); // task1 開始
    var task2 = Task<string>.Run(() => LongWaitingMethodReturnString(1000)); // task2 開始
    var task3 = Task<string>.Run(() => LongWaitingMethodReturnString(2000)); // task3 開始

    string[] ret = await Task.WhenAll(task1, task2, task3);
    Debug.WriteLine("task1,2,3 完了");

    // 戻り値
    Debug.WriteLine("戻り値1:" + ret[0]);
    Debug.WriteLine("戻り値2:" + ret[1]);
    Debug.WriteLine("戻り値3:" + ret[2]);
}

異なった型の戻り値を返すTaskをWhenAll

WhenAllで待つTaskが別々の戻り値の型を持つ場合でも、戻り値を受け取れる。
Taskが完了した後に、Resultプロパティを見る。

// 戻り値ありのTask(全部異なる型の戻り値)
private async void Button_Click_4(object sender, RoutedEventArgs e)
{
    var task1 = Task<string>.Run(() => { Thread.Sleep(3000); return "task1"; });    // task1 開始
    var task2 = Task<int>.Run(() => { Thread.Sleep(1000); return 2; });             // task2 開始
    var task3 = Task<double>.Run(() => { Thread.Sleep(2000); return 3.33; });       // task3 開始

    await Task.WhenAll(task1, task2, task3);
    Debug.WriteLine("task1,2,3 完了");

    // 戻り値
    Debug.WriteLine("戻り値1:" + task1.Result);
    Debug.WriteLine("戻り値2:" + task2.Result);
    Debug.WriteLine("戻り値3:" + task3.Result);
}

処理が終わる前にResultを見ると、task1.Wait()と同じ動きをする。(ここでブロックがかかるため、UIも固まるので注意)↓↓↓↓↓↓

// .Resultで、処理が終わるまでUIフリーズする例
private async void Button_Click_4(object sender, RoutedEventArgs e)
{
    var task1 = Task<string>.Run(() => { Thread.Sleep(5000); return "task1"; });    // task1 開始
    var task2 = Task<int>.Run(() => { Thread.Sleep(1000); return 2; });             // task2 開始
    var task3 = Task<double>.Run(() => { Thread.Sleep(2000); return 3.33; });       // task3 開始

    // 戻り値
    Debug.WriteLine("戻り値1:" + task1.Result); // ここでブロックかかり、task1が終わるまでの5秒間UIフリーズ
    Debug.WriteLine("戻り値2:" + task2.Result);
    Debug.WriteLine("戻り値3:" + task3.Result);

    await Task.WhenAll(task1, task2, task3);
    Debug.WriteLine("task1,2,3 完了");

}

TaskのWaitをするときは注意

こちらに書かれている通り、Waitをするときは注意。
Task内でawaitした後、Taskを呼んだ側でそのTaskをWaitしてしまうと、デッドロックする。

どうしてそうなるか?は、こちらの解説が分かりやすい。
Task.Run()したとき、awaitしたときのスレッドの動きが図解されている。

参考

Taskを極めろ!async/await完全攻略
https://qiita.com/acple@github/items/8f63aacb13de9954c5da

C# 今更ですが、await / async
https://qiita.com/rawr/items/5d49960a4e4d3823722f#%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%EF%BC%94%E3%81%A9%E3%81%AE%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89%E3%81%A7%E5%87%A6%E7%90%86%E3%81%8C%E5%AE%9F%E8%A1%8C%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%81%AE%E3%81%8B

++C++; // 未確認飛行 C
https://ufcpp.net/study/csharp/sp_thread.html

コード

11
13
0

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
11
13