回答編集履歴

2 微修正

tamoto

tamoto score 3999

2016/07/20 12:32  投稿

既に解決済みになってますが、少し気になったので回答をします。
質問者様の最初のコードは「非同期処理を順次繰り返す」という意図であるなら、正しいです。
ですが、出力は安定しないです。
理由は、Main関数のほうです。
Main関数を抜けると、プログラムが終了したという扱いになるので、非同期関数であるRunを呼び出した直後に、Runの動作を待たずにプログラムが先に終了しているのです。
なので、Runの呼び出しの後に長いSleepを配置することで↓
```csharp
public static void Main(string[] args) {
   Run ();
   Thread.Sleep (10000); // Runの実行が完了するまでプログラムを終了させない
}
```
意図している出力を確認することができるはずです。
もっと適切に待機するなら、
Run関数の戻り値をTask化して、Waitで待機するのが良いでしょう。
```csharp
public static void Main(string[] args) {
   Run () .Wait ();
}
public static async Task Run() {
   for (int i = 1; i < 10; i++) {
       ...
```
最後に、もし質問のコードで実際にやりたかったことが「複数のタスクを非同期で並列に実行」だとしたら、このコードでは意味が異なります。awaitは「指定した非同期処理が完了し次第、続きをさらに非同期で実行」するためのキーワードです。
---
07/20追記
目的が「複数のタスクを非同期で並列に実行」とのことなので、この場合は確かにParallelを使う解決法もあります。が、ちゃんとTaskを使う場合はどうすればよいのかを書いてみます。
並列実行のコードはこうです。
```csharp
public static void Run() {
   for (int i = 1; i < 10; i++) {
       Task.Run (() => {
           Thread.Sleep (1);
           System.Console.WriteLine ("hello{0}", i);
       });
   }
}
```
よく見ると元コードからasyncとawaitが外れただけですね。このコードの意味はどうでしょうか?
「WriteLineするだけのTaskをforで繰り返し生成してどんどん実行する」というそのままです。
このコードは書かれた通りに並列に動作します。
Runはあくまで非同期Taskを大量生成するだけなので、async関数ではなくなっているのがポイントです。
もちろん、この場合もWriteLine自体は非同期実行されるので、Main関数でSleepしないと意図した出力は得られないです。
並列実行なのでWriteLineの実際の実行順序が保証されなくなることは注意してください。
もし、この並列Runを適切に待機したいとなると、少しだけ工夫する必要があります。
「大量生成されたTaskを束ねて、その全てが完了したことを表すTaskを生成する」
```csharp
public static Task Run() {
   var list = new List<Task>(); // 生成するTaskを束ねるためのList
   for (int i = 1; i < 10; i++) {
       var task = Task.Run (() => {
           Thread.Sleep (1);
           System.Console.WriteLine ("hello{0}", i);
       });
       list.Add (task); // Taskを1つずつListへ
   }
   var tasks = list.ToArray (); // 集め終わったListを配列に
   return Task.WhenAll (tasks); // 全てのTaskを一つのTaskに
}
```
こうすることで、Main関数内でWaitで待機できるようになります。
---  
 
ところで、この並列実行コードには少し問題があります。
この行です。
`System.Console.WriteLine ("hello{0}", i);`
実は、各Taskがたった一つの変数`i`を共有しているため、
並列に実行されたWriteLineは全て`hello10`を出力してしまいます。
これを回避するには、for文のスコープ内で変数を確保し、それをTaskで使用すれば良いです。
```csharp
public static void Run() {
   for (int i = 1; i < 10; i++) {
       var x = i; // iをローカルスコープのxにコピー
       Task.Run (() => {
           Thread.Sleep (1);
           System.Console.WriteLine ("hello{0}", x);
       });
   }
}
```
おまけ。
こういうタイプの並列実行コードを記述するときはLinqを活用すると簡潔でいい感じに仕上がることが多いです。
Linqの扱いに慣れている必要はありますが、わかりやすさは段違いに高くなります。
参考までに、上記コードと同じ挙動のLinq使用版を置いておきます。
```csharp
public static Task Run()
{
   var tasks = Enumerable
       .Range(1, 10)
       .Range(1, 9)
       .Select(x => Task.Run(() =>
       {
           Thread.Sleep(1);
           System.Console.WriteLine("hello{0}", x);
       }))
       .ToArray();
   return Task.WhenAll(tasks);
}
// asyncラムダ使用版、こっちのほうが推奨
public static Task Run()
{
   var tasks = Enumerable
       .Range(1, 10)
       .Range(1, 9)
       .Select(async x =>
       {
           await Task.Delay(1);
           System.Console.WriteLine("hello{0}", x);
       })
       .ToArray();
   return Task.WhenAll(tasks);
}
```
1 追記

tamoto

tamoto score 3999

2016/07/20 12:25  投稿

既に解決済みになってますが、少し気になったので回答をします。
質問者様の最初のコードは「非同期処理を順次繰り返す」という意図であるなら、正しいです。
ですが、出力は安定しないです。
理由は、Main関数のほうです。
Main関数を抜けると、プログラムが終了したという扱いになるので、非同期関数であるRunを呼び出した直後に、Runの動作を待たずにプログラムが先に終了しているのです。
なので、Runの呼び出しの後に長いSleepを配置することで↓
```csharp
public static void Main(string[] args) {
   Run ();
   Thread.Sleep (10000); // Runの実行が完了するまでプログラムを終了させない
}
```
意図している出力を確認することができるはずです。
もっと適切に待機するなら、
Run関数の戻り値をTask化して、Waitで待機するのが良いでしょう。
```csharp
public static void Main(string[] args) {
   Run () .Wait ();
}
public static async Task Run() {
   for (int i = 1; i < 10; i++) {
       ...
```
最後に、もし質問のコードで実際にやりたかったことが「複数のタスクを非同期で並列に実行」だとしたら、このコードでは意味が異なります。awaitは「指定した非同期処理が完了し次第、続きをさらに非同期で実行」するためのキーワードです。
最後に、もし質問のコードで実際にやりたかったことが「複数のタスクを非同期で並列に実行」だとしたら、このコードでは意味が異なります。awaitは「指定した非同期処理が完了し次第、続きをさらに非同期で実行」するためのキーワードです。
---
07/20追記
目的が「複数のタスクを非同期で並列に実行」とのことなので、この場合は確かにParallelを使う解決法もあります。が、ちゃんとTaskを使う場合はどうすればよいのかを書いてみます。
並列実行のコードはこうです。
```csharp
public static void Run() {
   for (int i = 1; i < 10; i++) {
       Task.Run (() => {
           Thread.Sleep (1);
           System.Console.WriteLine ("hello{0}", i);
       });
   }
}
```
よく見ると元コードからasyncとawaitが外れただけですね。このコードの意味はどうでしょうか?
「WriteLineするだけのTaskをforで繰り返し生成してどんどん実行する」というそのままです。
このコードは書かれた通りに並列に動作します。
Runはあくまで非同期Taskを大量生成するだけなので、async関数ではなくなっているのがポイントです。
もちろん、この場合もWriteLine自体は非同期実行されるので、Main関数でSleepしないと意図した出力は得られないです。
並列実行なのでWriteLineの実際の実行順序が保証されなくなることは注意してください。
もし、この並列Runを適切に待機したいとなると、少しだけ工夫する必要があります。
「大量生成されたTaskを束ねて、その全てが完了したことを表すTaskを生成する」
```csharp
public static Task Run() {
   var list = new List<Task>(); // 生成するTaskを束ねるためのList
   for (int i = 1; i < 10; i++) {
       var task = Task.Run (() => {
           Thread.Sleep (1);
           System.Console.WriteLine ("hello{0}", i);
       });
       list.Add (task); // Taskを1つずつListへ
   }
   var tasks = list.ToArray (); // 集め終わったListを配列に
   return Task.WhenAll (tasks); // 全てのTaskを一つのTaskに
}
```
こうすることで、Main関数内でWaitで待機できるようになります。
ところで、この並列実行コードには少し問題があります。
この行です。
`System.Console.WriteLine ("hello{0}", i);`
実は、各Taskがたった一つの変数`i`を共有しているため、
並列に実行されたWriteLineは全て`hello10`を出力してしまいます。
これを回避するには、for文のスコープ内で変数を確保し、それをTaskで使用すれば良いです。
```csharp
public static void Run() {
   for (int i = 1; i < 10; i++) {
       var x = i; // iをローカルスコープのxにコピー
       Task.Run (() => {
           Thread.Sleep (1);
           System.Console.WriteLine ("hello{0}", x);
       });
   }
}
```
おまけ。
こういうタイプの並列実行コードを記述するときはLinqを活用すると簡潔でいい感じに仕上がることが多いです。
Linqの扱いに慣れている必要はありますが、わかりやすさは段違いに高くなります。
参考までに、上記コードと同じ挙動のLinq使用版を置いておきます。
```csharp
public static Task Run()
{
   var tasks = Enumerable
       .Range(1, 10)
       .Select(x => Task.Run(() =>
       {
           Thread.Sleep(1);
           System.Console.WriteLine("hello{0}", x);
       }))
       .ToArray();
   return Task.WhenAll(tasks);
}
// asyncラムダ使用版、こっちのほうが推奨
public static Task Run()
{
   var tasks = Enumerable
       .Range(1, 10)
       .Select(async x =>
       {
           await Task.Delay(1);
           System.Console.WriteLine("hello{0}", x);
       })
       .ToArray();
   return Task.WhenAll(tasks);
}
```

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