質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.48%

C# Taskについてawait task.WhenAll()のとき,awaitの有無の違い

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 10K+

score 259

ひとつ前の質問は解決したのですが(↓),Taskを待機するときのawaitの有無による動作の違いについて教えてください.
Teratail:C#で並列処理,すべて終わるまで待機

どちらも以下のコードを実行した場合の実行結果は同じ(に見える)のですが,13行目のawaitの有無,foreach内のresultの違いによりどのような違いが生じるのでしょうか.

よろしくお願いします.

コード1

static void Main() {
            fuga2();
            Console.ReadLine();
        }

        static async void fuga2() {
            Console.WriteLine("fuga start");

            var tasks = new List<Task<int>>();
            tasks.Add(Task.Run(() => Heavy("1")));
            tasks.Add(Task.Run(() => Heavy("2")));

            var result = await Task.WhenAll(tasks.ToArray());

            foreach (var r in result) {
                Console.WriteLine(r);
            }
        }


        static int Heavy(string str) {
            Console.WriteLine(str + "Start");
            for (int i = 0; i < 200000000; i++) ;//重たい処理
            Console.WriteLine(str + "End");

            return 5; //ダミーデータ
        }

コード2

static void Main() {
            fuga2();
            Console.ReadLine();
        }

        static async void fuga2() {
            Console.WriteLine("fuga start");

            var tasks = new List<Task<int>>();
            tasks.Add(Task.Run(() => Heavy("1")));
            tasks.Add(Task.Run(() => Heavy("2")));

            var result = Task.WhenAll(tasks.ToArray());  //←awaitがない

            foreach (var r in result.Result) {           //←resultがresult.Resultになっている
                Console.WriteLine(r);
            }
        }


        static int Heavy(string str) {
            Console.WriteLine(str + "Start");
            for (int i = 0; i < 200000000; i++) ;//重たい処理
            Console.WriteLine(str + "End");

            return 5; //ダミーデータ
        }
fuga start
2Start
1Start
//ここで数秒待たされる
2End
1End
5
5
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+2

こんにちは。
前回の質問のコメントと今回の質問では状況がやや異なるので、両方に回答します。

まず、この質問の状況で.Resultを使用するのは絶対に避けてください。
awaitは非同期メソッド特有のキーワードで、「awaitが書かれた箇所で処理をぶつ切りにし、前回の処理が完了し次第続きの処理を自動で起動する」という機能です。待機が発生しうる処理をコールバックの連鎖に変換することで、「非同期」を実現しているわけです。結果、非同期メソッドはそのメソッドの見た目に反してかなり複雑なコード生成が行われます。
一方、.Resultは、「本来非同期に実行されるTaskを、その結果が出るまで待機して結果を取り出す」もので、非同期に動作するTaskを同期コンテキストで待機することになります。
実際、限られた状況ではどちらでも同じ結果を得ることが可能です。しかし、.Resultでは非同期のメリットが一切得られなくなるため、このシーンで採用する価値は皆無です。
さらに、.Resultの手法には大きな問題があります。それは、「非同期コンテキストを無理に同期することになるため、デッドロックを引き起こす可能性がある」ことです。これはちょっとしたコードでも簡単に発生するので、「使っても絶対に安全な場面である」「何故安全であるか理解している」「その上で.Resultを使うことに説得力がある理由が存在する」を全て満たす場合にのみ使ってください。つまりは、実質使用禁止ということです。


ちなみに、前回の質問のコメントでtasks.ForEach(t=>t.Result);としているのは、その手法をあえて採用する価値が全く無いことは置いといて、「その直前でawait Task.WhenAll(tasks.ToArray());をしている場合は安全」です。何故かと言うと、WhenAllによって「tasksの全てのTaskが完全に完了していることが保証されている」からです。これの意味が完全に分かる頃には、どうして.Resultを使ってはいけないか、どこに.Resultを使っても安全か、も分かっているはずです。

以下に前回の質問のコードの重点を整理しました。どちらでも同じ結果は得られるため、「あえて推奨されないやり方を選ぶ理由があるならそうすれば良い」というのが結論ですね。ただし、「Task.WhenAllawaitしない」というのは論外です。

推奨されるやり方:

var result = await Task.WhenAll(tasks.ToArray());
foreach (var x in result)
    Console.WriteLine(x);

推奨されないやり方:

await Task.WhenAll(tasks.ToArray());
tasks.ForEach(x => x.Result);

論外、デッドロックが発生する可能性がある:

var result = Task.WhenAll(tasks.ToArray());
tasks.ForEach(x => x.Result);

/*---------------------------------------*/

var result = Task.WhenAll(tasks.ToArray());
foreach (var r in result.Result)
    Console.WriteLine(r);

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/02/05 12:43

    回答ありがとうございます.

    >非同期に動作するTaskを同期コンテキストで待機することになります
    ということは例えばGUIのボタンイベント内に書いたらそこでフリーズする,ということでしょうか
    Consoleだったからその違いに気が付かなかったのですね.なるほど...

    このあたりの理解が全く足りていないのでもう少し勉強します….

    キャンセル

  • 2019/02/05 13:21

    そうですね、ボタンイベント内なら待機するコンテキストがUIスレッドとなるので画面のフリーズを起こします。
    そしておそらく、生のResultを使うとそのフリーズが永続化することになるので危険です。

    キャンセル

+1

Taskについて完璧に理解していてアプリケーションの動作も完全に把握してない限り、
Task.Resultは使っちゃ駄目です。
非同期プログラミングのベスト プラクティス
中ほどまで読むと出てきます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/02/04 19:16 編集

    回答ありがとうございます.
    ひとつ前の質問に対する答え,ということでよろしいでしょうか…?

    いただいたURL,きっちり最後まで読みます.

    キャンセル

  • 2019/02/04 19:22

    ``foreach (var r in result.Result) { //←resultがresult.Resultになっている``
    ここですよ

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.48%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る