回答編集履歴

2 大事なこと

tamoto

tamoto score 3997

2019/02/05 09:35  投稿

こんにちは。
前回の質問のコメントと今回の質問では状況がやや異なるので、両方に回答します。
まず、この質問の状況で`.Result`を使用するのは絶対に避けてください。
`await`は非同期メソッド特有のキーワードで、「awaitが書かれた箇所で処理をぶつ切りにし、前回の処理が完了し次第続きの処理を自動で起動する」という機能です。待機が発生しうる処理をコールバックの連鎖に変換することで、「非同期」を実現しているわけです。結果、非同期メソッドはそのメソッドの見た目に反してかなり複雑なコード生成が行われます。
一方、`.Result`は、「本来非同期に実行されるTaskを、その結果が出るまで待機して結果を取り出す」もので、非同期に動作するTaskを同期コンテキストで待機することになります。
実際、**限られた状況ではどちらでも同じ結果を得ることが可能**です。しかし、`.Result`では非同期のメリットが一切得られなくなるため、このシーンで採用する価値は皆無です。
さらに、`.Result`の手法には大きな問題があります。それは、「非同期コンテキストを無理に同期することになるため、デッドロックを引き起こす可能性がある」ことです。これはちょっとしたコードでも簡単に発生するので、「使っても絶対に安全な場面である」「何故安全であるか理解している」「その上で`.Result`を使うことに説得力がある理由が存在する」を全て満たす場合にのみ使ってください。つまりは、実質使用禁止ということです。
---
ちなみに、前回の質問のコメントで`tasks.foreach(t=>t.Result);`としているのは、その手法をあえて採用する価値が全く無いことは置いといて、「その直前で`await Task.WhenAll(tasks.ToArray());`をしている場合は安全」です。何故かと言うと、`WhenAll`によって「`tasks`の全てのTaskが完全に完了していることが保証されている」からです。これの意味が完全に分かる頃には、どうして`.Result`を使ってはいけないか、どこに`.Result`を使っても安全か、も分かっているはずです。
ちなみに、前回の質問のコメントで`tasks.ForEach(t=>t.Result);`としているのは、その手法をあえて採用する価値が全く無いことは置いといて、「その直前で`await Task.WhenAll(tasks.ToArray());`をしている場合は安全」です。何故かと言うと、`WhenAll`によって「`tasks`の全てのTaskが完全に完了していることが保証されている」からです。これの意味が完全に分かる頃には、どうして`.Result`を使ってはいけないか、どこに`.Result`を使っても安全か、も分かっているはずです。
以下に前回の質問のコードの重点を整理しました。**どちらでも同じ結果は得られる**ため、「**あえて推奨されないやり方を選ぶ理由があるならそうすれば良い**」というのが結論ですね。
以下に前回の質問のコードの重点を整理しました。**どちらでも同じ結果は得られる**ため、「**あえて推奨されないやり方を選ぶ理由があるならそうすれば良い**」というのが結論ですね。ただし、「`Task.WhenAll`を`await`しない」というのは論外です。
推奨されるやり方:
```csharp
var result = await Task.WhenAll(tasks.ToArray());
foreach (var x in result)
   Console.WriteLine(x);
```
推奨されないやり方:
```csharp
await Task.WhenAll(tasks.ToArray());
tasks.ForEach(x => x.Result);
```  
 
 
論外、デッドロックが発生する可能性がある:  
```csharp  
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);  
```
1 追記

tamoto

tamoto score 3997

2019/02/05 09:29  投稿

こんにちは。
前回の質問のコメントと今回の質問では状況がやや異なるので、両方に回答します。
まず、この質問の状況で`.Result`を使用するのは絶対に避けてください。
`await`は非同期メソッド特有のキーワードで、「awaitが書かれた箇所で処理をぶつ切りにし、前回の処理が完了し次第続きの処理を自動で起動する」という機能です。待機が発生しうる処理をコールバックの連鎖に変換することで、「非同期」を実現しているわけです。結果、非同期メソッドはそのメソッドの見た目に反してかなり複雑なコード生成が行われます。
一方、`.Result`は、「本来非同期に実行されるTaskを、その結果が出るまで待機して結果を取り出す」もので、非同期に動作するTaskを同期コンテキストで待機することになります。
実際、**限られた状況ではどちらでも同じ結果を得ることが可能**です。しかし、`.Result`では非同期のメリットが一切得られなくなるため、このシーンで採用する価値は皆無です。
さらに、`.Result`の手法には大きな問題があります。それは、「非同期コンテキストを無理に同期することになるため、デッドロックを引き起こす可能性がある」ことです。これはちょっとしたコードでも簡単に発生するので、「使っても絶対に安全な場面である」「何故安全であるか理解している」「その上で`.Result`を使うことに説得力がある理由が存在する」を全て満たす場合にのみ使ってください。つまりは、実質使用禁止ということです。
ちなみに、前回の質問のコメントで`tasks.foreach(t=>t.Result);`としているのは、その手法をあえて採用する価値が全く無いことは置いといて、「その直前で`await Task.WhenAll(tasks.ToArray());`をしている場合は安全」です。何故かと言うと、`WhenAll`によって「`tasks`の全てのTaskが完全に完了していることが保証されている」からです。これの意味が完全に分かる頃には、どうして`.Result`を使ってはいけないか、どこに`.Result`を使っても安全か、も分かっているはずです。
---
ちなみに、前回の質問のコメントで`tasks.foreach(t=>t.Result);`としているのは、その手法をあえて採用する価値が全く無いことは置いといて、「その直前で`await Task.WhenAll(tasks.ToArray());`をしている場合は安全」です。何故かと言うと、`WhenAll`によって「`tasks`の全てのTaskが完全に完了していることが保証されている」からです。これの意味が完全に分かる頃には、どうして`.Result`を使ってはいけないか、どこに`.Result`を使っても安全か、も分かっているはずです。
以下に前回の質問のコードの重点を整理しました。**どちらでも同じ結果は得られる**ため、「**あえて推奨されないやり方を選ぶ理由があるならそうすれば良い**」というのが結論ですね。
推奨されるやり方:
```csharp
var result = await Task.WhenAll(tasks.ToArray());
foreach (var x in result)
   Console.WriteLine(x);
```
推奨されないやり方:
```csharp
await Task.WhenAll(tasks.ToArray());
tasks.ForEach(x => x.Result);
```

思考するエンジニアのためのQ&Aサイト「teratail」について詳しく知る