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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

解決済

2回答

16393閲覧

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

ElecDove

総合スコア254

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

1グッド

1クリップ

投稿2019/02/04 09:58

編集2019/02/04 10:11

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

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

よろしくお願いします.

コード1

C#

1static void Main() { 2 fuga2(); 3 Console.ReadLine(); 4 } 5 6 static async void fuga2() { 7 Console.WriteLine("fuga start"); 8 9 var tasks = new List<Task<int>>(); 10 tasks.Add(Task.Run(() => Heavy("1"))); 11 tasks.Add(Task.Run(() => Heavy("2"))); 12 13 var result = await Task.WhenAll(tasks.ToArray()); 14 15 foreach (var r in result) { 16 Console.WriteLine(r); 17 } 18 } 19 20 21 static int Heavy(string str) { 22 Console.WriteLine(str + "Start"); 23 for (int i = 0; i < 200000000; i++) ;//重たい処理 24 Console.WriteLine(str + "End"); 25 26 return 5; //ダミーデータ 27 } 28

コード2

C#

1static void Main() { 2 fuga2(); 3 Console.ReadLine(); 4 } 5 6 static async void fuga2() { 7 Console.WriteLine("fuga start"); 8 9 var tasks = new List<Task<int>>(); 10 tasks.Add(Task.Run(() => Heavy("1"))); 11 tasks.Add(Task.Run(() => Heavy("2"))); 12 13 var result = Task.WhenAll(tasks.ToArray()); //←awaitがない 14 15 foreach (var r in result.Result) { //←resultがresult.Resultになっている 16 Console.WriteLine(r); 17 } 18 } 19 20 21 static int Heavy(string str) { 22 Console.WriteLine(str + "Start"); 23 for (int i = 0; i < 200000000; i++) ;//重たい処理 24 Console.WriteLine(str + "End"); 25 26 return 5; //ダミーデータ 27 } 28
fuga start 2Start 1Start //ここで数秒待たされる 2End 1End 5 5
kitajima_kotaro👍を押しています

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

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

まず、この質問の状況で.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しない」というのは論外です。

推奨されるやり方:

csharp

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

推奨されないやり方:

csharp

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

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

csharp

1var result = Task.WhenAll(tasks.ToArray()); 2tasks.ForEach(x => x.Result); 3 4/*---------------------------------------*/ 5 6var result = Task.WhenAll(tasks.ToArray()); 7foreach (var r in result.Result) 8 Console.WriteLine(r);

投稿2019/02/05 00:23

編集2019/02/05 00:35
tamoto

総合スコア4105

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ElecDove

2019/02/05 03:43

回答ありがとうございます. >非同期に動作するTaskを同期コンテキストで待機することになります ということは例えばGUIのボタンイベント内に書いたらそこでフリーズする,ということでしょうか Consoleだったからその違いに気が付かなかったのですね.なるほど... このあたりの理解が全く足りていないのでもう少し勉強します….
tamoto

2019/02/05 04:21

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

0

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

投稿2019/02/04 10:11

hihijiji

総合スコア4150

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ElecDove

2019/02/04 10:16 編集

回答ありがとうございます. ひとつ前の質問に対する答え,ということでよろしいでしょうか…? いただいたURL,きっちり最後まで読みます.
hihijiji

2019/02/04 10:22

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問