回答編集履歴

2

微修正

2016/07/20 03:32

投稿

tamoto
tamoto

スコア4105

test CHANGED
@@ -134,6 +134,10 @@
134
134
 
135
135
 
136
136
 
137
+ ---
138
+
139
+
140
+
137
141
  ところで、この並列実行コードには少し問題があります。
138
142
 
139
143
  この行です。
@@ -186,7 +190,7 @@
186
190
 
187
191
  var tasks = Enumerable
188
192
 
189
- .Range(1, 10)
193
+ .Range(1, 9)
190
194
 
191
195
  .Select(x => Task.Run(() =>
192
196
 
@@ -216,7 +220,7 @@
216
220
 
217
221
  var tasks = Enumerable
218
222
 
219
- .Range(1, 10)
223
+ .Range(1, 9)
220
224
 
221
225
  .Select(async x =>
222
226
 

1

追記

2016/07/20 03:32

投稿

tamoto
tamoto

スコア4105

test CHANGED
@@ -51,3 +51,189 @@
51
51
 
52
52
 
53
53
  最後に、もし質問のコードで実際にやりたかったことが「複数のタスクを非同期で並列に実行」だとしたら、このコードでは意味が異なります。awaitは「指定した非同期処理が完了し次第、続きをさらに非同期で実行」するためのキーワードです。
54
+
55
+
56
+
57
+ ---
58
+
59
+
60
+
61
+ 07/20追記
62
+
63
+ 目的が「複数のタスクを非同期で並列に実行」とのことなので、この場合は確かにParallelを使う解決法もあります。が、ちゃんとTaskを使う場合はどうすればよいのかを書いてみます。
64
+
65
+ 並列実行のコードはこうです。
66
+
67
+ ```csharp
68
+
69
+ public static void Run() {
70
+
71
+ for (int i = 1; i < 10; i++) {
72
+
73
+ Task.Run (() => {
74
+
75
+ Thread.Sleep (1);
76
+
77
+ System.Console.WriteLine ("hello{0}", i);
78
+
79
+ });
80
+
81
+ }
82
+
83
+ }
84
+
85
+ ```
86
+
87
+ よく見ると元コードからasyncとawaitが外れただけですね。このコードの意味はどうでしょうか?
88
+
89
+ 「WriteLineするだけのTaskをforで繰り返し生成してどんどん実行する」というそのままです。
90
+
91
+ このコードは書かれた通りに並列に動作します。
92
+
93
+ Runはあくまで非同期Taskを大量生成するだけなので、async関数ではなくなっているのがポイントです。
94
+
95
+ もちろん、この場合もWriteLine自体は非同期実行されるので、Main関数でSleepしないと意図した出力は得られないです。
96
+
97
+ 並列実行なのでWriteLineの実際の実行順序が保証されなくなることは注意してください。
98
+
99
+
100
+
101
+ もし、この並列Runを適切に待機したいとなると、少しだけ工夫する必要があります。
102
+
103
+ 「大量生成されたTaskを束ねて、その全てが完了したことを表すTaskを生成する」
104
+
105
+ ```csharp
106
+
107
+ public static Task Run() {
108
+
109
+ var list = new List<Task>(); // 生成するTaskを束ねるためのList
110
+
111
+ for (int i = 1; i < 10; i++) {
112
+
113
+ var task = Task.Run (() => {
114
+
115
+ Thread.Sleep (1);
116
+
117
+ System.Console.WriteLine ("hello{0}", i);
118
+
119
+ });
120
+
121
+ list.Add (task); // Taskを1つずつListへ
122
+
123
+ }
124
+
125
+ var tasks = list.ToArray (); // 集め終わったListを配列に
126
+
127
+ return Task.WhenAll (tasks); // 全てのTaskを一つのTaskに
128
+
129
+ }
130
+
131
+ ```
132
+
133
+ こうすることで、Main関数内でWaitで待機できるようになります。
134
+
135
+
136
+
137
+ ところで、この並列実行コードには少し問題があります。
138
+
139
+ この行です。
140
+
141
+ `System.Console.WriteLine ("hello{0}", i);`
142
+
143
+ 実は、各Taskがたった一つの変数`i`を共有しているため、
144
+
145
+ 並列に実行されたWriteLineは全て`hello10`を出力してしまいます。
146
+
147
+ これを回避するには、for文のスコープ内で変数を確保し、それをTaskで使用すれば良いです。
148
+
149
+ ```csharp
150
+
151
+ public static void Run() {
152
+
153
+ for (int i = 1; i < 10; i++) {
154
+
155
+ var x = i; // iをローカルスコープのxにコピー
156
+
157
+ Task.Run (() => {
158
+
159
+ Thread.Sleep (1);
160
+
161
+ System.Console.WriteLine ("hello{0}", x);
162
+
163
+ });
164
+
165
+ }
166
+
167
+ }
168
+
169
+ ```
170
+
171
+
172
+
173
+ おまけ。
174
+
175
+ こういうタイプの並列実行コードを記述するときはLinqを活用すると簡潔でいい感じに仕上がることが多いです。
176
+
177
+ Linqの扱いに慣れている必要はありますが、わかりやすさは段違いに高くなります。
178
+
179
+ 参考までに、上記コードと同じ挙動のLinq使用版を置いておきます。
180
+
181
+ ```csharp
182
+
183
+ public static Task Run()
184
+
185
+ {
186
+
187
+ var tasks = Enumerable
188
+
189
+ .Range(1, 10)
190
+
191
+ .Select(x => Task.Run(() =>
192
+
193
+ {
194
+
195
+ Thread.Sleep(1);
196
+
197
+ System.Console.WriteLine("hello{0}", x);
198
+
199
+ }))
200
+
201
+ .ToArray();
202
+
203
+
204
+
205
+ return Task.WhenAll(tasks);
206
+
207
+ }
208
+
209
+
210
+
211
+ // asyncラムダ使用版、こっちのほうが推奨
212
+
213
+ public static Task Run()
214
+
215
+ {
216
+
217
+ var tasks = Enumerable
218
+
219
+ .Range(1, 10)
220
+
221
+ .Select(async x =>
222
+
223
+ {
224
+
225
+ await Task.Delay(1);
226
+
227
+ System.Console.WriteLine("hello{0}", x);
228
+
229
+ })
230
+
231
+ .ToArray();
232
+
233
+
234
+
235
+ return Task.WhenAll(tasks);
236
+
237
+ }
238
+
239
+ ```