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