質問編集履歴

6

誤字修正

2016/09/20 18:42

投稿

MagoCat
MagoCat

スコア86

test CHANGED
File without changes
test CHANGED
@@ -370,7 +370,7 @@
370
370
 
371
371
 
372
372
 
373
- ```ここに言語を入力
373
+ ```c#
374
374
 
375
375
  private void button2_Click(object sender, EventArgs e)
376
376
 

5

誤字修正

2016/09/20 18:42

投稿

MagoCat
MagoCat

スコア86

test CHANGED
File without changes
test CHANGED
@@ -466,4 +466,4 @@
466
466
 
467
467
 
468
468
 
469
- 追伸:他の方からコメントで頂いたURLはまだ読んでいないので、今日明日のうちに目を通してみます。
469
+ 追伸:皆さんからコメントで頂いたURLはまだ読んでいないので、今日明日のうちに目を通してみます。

4

誤字修正

2016/09/20 18:10

投稿

MagoCat
MagoCat

スコア86

test CHANGED
File without changes
test CHANGED
@@ -320,7 +320,7 @@
320
320
 
321
321
 
322
322
 
323
- - メインスレッドのWait()到達よりもタスク内でのmyAtl.GetThreadID()実行が確実に遅れるように、タスク内の最初で数秒スリープするようにした。
323
+ - メインスレッドの```task.Wait();```到達よりもタスク内での```myAtl.GetThreadID();```実行が確実に遅れるように、タスク内の最初で数秒スリープするようにした。
324
324
 
325
325
 
326
326
 
@@ -336,7 +336,7 @@
336
336
 
337
337
 
338
338
 
339
- 具体的には、まずCMyATLTestClassのGetThreadIDの中身を以下のように変更しました。
339
+ 具体的には、まずCMyATLTestClassのGetThreadID()の中身を以下のように変更しました。
340
340
 
341
341
 
342
342
 

3

コメントで頂いたアドバイスを反映した追加実験を行った事による追記

2016/09/20 18:07

投稿

MagoCat
MagoCat

スコア86

test CHANGED
File without changes
test CHANGED
@@ -301,3 +301,169 @@
301
301
 
302
302
 
303
303
  この挙動を説明するMSDNの記述を見つけたいところですね。
304
+
305
+
306
+
307
+
308
+
309
+
310
+
311
+
312
+
313
+ ---
314
+
315
+ **【追記3】**
316
+
317
+
318
+
319
+ [追記2]の実験に幾つかの改良を加えました。
320
+
321
+
322
+
323
+ - メインスレッドのWait()到達よりもタスク内でのmyAtl.GetThreadID()実行が確実に遅れるように、タスク内の最初で数秒スリープするようにした。
324
+
325
+
326
+
327
+ - デバッグ出力文字列にスレッドIDを添えるようにした。
328
+
329
+
330
+
331
+ - COMメソッド内部でもデバッグ文字列を出力するようにした。
332
+
333
+
334
+
335
+
336
+
337
+
338
+
339
+ 具体的には、まずCMyATLTestClassのGetThreadIDの中身を以下のように変更しました。
340
+
341
+
342
+
343
+ ```c++
344
+
345
+ STDMETHODIMP CMyATLTestClass::GetThreadID(LONG* id)
346
+
347
+ {
348
+
349
+ *id = GetCurrentThreadId();
350
+
351
+
352
+
353
+ CString str;
354
+
355
+ str.Format(_T("InGetThreadID ThreadID=%d\n"), *id);
356
+
357
+ OutputDebugString(str);
358
+
359
+
360
+
361
+ return S_OK;
362
+
363
+ }
364
+
365
+ ```
366
+
367
+
368
+
369
+ また、このCOMを利用するWindowsForms側のButton2クリック時の処理を以下のように変更しました。
370
+
371
+
372
+
373
+ ```ここに言語を入力
374
+
375
+ private void button2_Click(object sender, EventArgs e)
376
+
377
+ {
378
+
379
+ //ボタン2では、GetThreadIDをTask内(スレッドプール)で呼ぶ。
380
+
381
+
382
+
383
+ int threadID = 0;
384
+
385
+ Task task = Task.Run(() =>
386
+
387
+ {
388
+
389
+ //3秒待機すれば、
390
+
391
+ //メインスレッドは間違いなくWait()に突入しているだろう。
392
+
393
+ Thread.Sleep(3000);
394
+
395
+
396
+
397
+ Trace.WriteLine("TaskStart ThreadID=" + AppDomain.GetCurrentThreadId());
398
+
399
+ myAtl.GetThreadID(out threadID);
400
+
401
+ Trace.WriteLine("TaskEnd ThreadID=" + AppDomain.GetCurrentThreadId());
402
+
403
+ });
404
+
405
+
406
+
407
+ //タスクの完了を待機してから、メッセージボックスを使って表示
408
+
409
+ Trace.WriteLine("BeforeWait ThreadID=" + AppDomain.GetCurrentThreadId());
410
+
411
+ task.Wait();
412
+
413
+ Trace.WriteLine("AfterWait ThreadID=" + AppDomain.GetCurrentThreadId());
414
+
415
+ MessageBox.Show("ボタン2 ThreadID=" + threadID);
416
+
417
+ }
418
+
419
+ ```
420
+
421
+
422
+
423
+ Button2をクリックすることに依るデバッグ出力の結果は以下のように成りました。
424
+
425
+ (折角なので、紹介して頂いたDebugViewを使用しています)
426
+
427
+
428
+
429
+ ![イメージ説明](a65210e5e2e1e1bdb9ec83018d8ec401.png)
430
+
431
+
432
+
433
+ 順序はこうです。
434
+
435
+
436
+
437
+ **1. "BeforeWait"がメインスレッド(ID=6128)で出力された。**
438
+
439
+
440
+
441
+ **2. "TaskStart"が別スレッド(ID=7964)で出力された。**
442
+
443
+ (タスクの最初に3秒スリープしたことで、1と2の出力には大きな時間差があります。この間にメインスレッドは確実に```task.Wait();```に突入しているでしょう。)
444
+
445
+
446
+
447
+ **3. "InGetThreadID"がメインスレッド(ID=6128)で出力された。**
448
+
449
+ (メインスレッドは```task.Wait();```でブロックされているはずなのにメインスレッドでCOMのメソッドが実行されています)
450
+
451
+
452
+
453
+ **4. "TaskEnd"が別スレッド(ID=7964)で出力された。**
454
+
455
+
456
+
457
+ **5. "AfterWait"がメインスレッド(ID=6128)で出力された。**
458
+
459
+ (このタイミングでようやくメインスレッドのブロッキングは解除されています)
460
+
461
+
462
+
463
+ つまりこの実験から何が分かるのかというと、```task.Wait();```による待ちでスレッドの処理が完全にブロックされるのではなくて、別スレッドからSTA-COMメソッドを呼び出したときに発生するメッセージは特別扱いで処理できるようだということです。
464
+
465
+
466
+
467
+
468
+
469
+ 追伸:他の方からコメントで頂いたURLはまだ読んでいないので、今日明日のうちに目を通してみます。

2

回答者からの指摘を受けて追加実験を行ったことに依る追記

2016/09/20 18:04

投稿

MagoCat
MagoCat

スコア86

test CHANGED
File without changes
test CHANGED
@@ -213,3 +213,91 @@
213
213
 
214
214
 
215
215
  つまりは、Taskを使用して別スレッドから呼び出されたCOMのGetThreadIDは確実にメインスレッドのメッセージループで実行されているようなので、Button2内のWait()でデッドロックしない理由については未だ納得できない状態です。
216
+
217
+
218
+
219
+
220
+
221
+
222
+
223
+
224
+
225
+ ---
226
+
227
+ **【追記2】**
228
+
229
+ 「もしかするとTask.Waitでメインスレッドが完全にブロッキングされる訳ではなくCOMメッセージの処理に限っては継続されるのではないか」という旨の指摘をいただきましたので、Trace.WriteLineを用いて処理順序を追ってみました。
230
+
231
+
232
+
233
+ 具体的には、ボタン2クリック時の処理にTrace.WriteLineを何行か追加して、次のようにしました。
234
+
235
+
236
+
237
+ ```C#
238
+
239
+ private void button2_Click(object sender, EventArgs e)
240
+
241
+ {
242
+
243
+ //ボタン2では、GetThreadIDをTask内(スレッドプール)で呼ぶ。
244
+
245
+ int threadID = 0;
246
+
247
+ Task task = Task.Run(() =>
248
+
249
+ {
250
+
251
+ Trace.WriteLine("TaskStart");
252
+
253
+ myAtl.GetThreadID(out threadID);
254
+
255
+ Trace.WriteLine("TaskEnd");
256
+
257
+ });
258
+
259
+
260
+
261
+ //タスクの完了を待機してから、メッセージボックスを使って表示
262
+
263
+ Trace.WriteLine("BeforeWait");
264
+
265
+ task.Wait();
266
+
267
+ Trace.WriteLine("AfterWait");
268
+
269
+ MessageBox.Show("ボタン2 ThreadID=" + threadID);
270
+
271
+ }
272
+
273
+ ```
274
+
275
+
276
+
277
+ デバッグ実行してボタン2をクリックしたときの出力画面の様子は以下のとおりです。
278
+
279
+
280
+
281
+ ![イメージ説明](a71da1e055cc7fb8a59b51420c559649.png)
282
+
283
+
284
+
285
+ TaskStart->BeforeWait->TaskEnd->AfterWait の順で処理が進行したようです。
286
+
287
+
288
+
289
+ "BeforeWait"が出力された直後には```task.Wait();```が行われてメインスレッドがブロッキングされている。
290
+
291
+ にも関わらず、メインスレッドのメッセージループで実行されるべき```myAtl.GetThreadID(out threadID);```を含むタスクが無事終了していることが "TaskEnd" の出力から分かります。
292
+
293
+
294
+
295
+ ここで、```myAtl.GetThreadID(out threadID);```の行が```task.Wait();```よりも前に処理されたか```task.Wait();```の中で処理されたかですが、中で処理されたと考えるほうがありえそうに思います。なぜなら```task.Wait();```よりも前には他のメッセージの割り込みを許しそうなコードは一切ないからです。
296
+
297
+
298
+
299
+ かといって```task.Wait();```によるメインスレッドのブロッキング中に、COMのメッセージだけは選択的に処理されるという動作が自然かというと私は結構不自然だと思いました。
300
+
301
+
302
+
303
+ この挙動を説明するMSDNの記述を見つけたいところですね。

1

追加実験を行ったことに依る追記

2016/09/19 20:37

投稿

MagoCat
MagoCat

スコア86

test CHANGED
File without changes
test CHANGED
@@ -143,3 +143,73 @@
143
143
 
144
144
 
145
145
  考察の穴を解消してください。
146
+
147
+
148
+
149
+
150
+
151
+
152
+
153
+
154
+
155
+ ---
156
+
157
+ **【追記】**
158
+
159
+ 「もしかするとCOMオブジェクト生成時に別スレッドが起動されその中でメッセージループが回っているからデッドロックが回避されているのではないか」との指摘がありましたので、メインスレッドのスレッドIDを(COMを経由せず)表示するコードをForm1に追加してもう一度実験を行いました。
160
+
161
+ 追加したコードは下記のとおりです。
162
+
163
+
164
+
165
+ ```C#
166
+
167
+ private void button0_Click(object sender, EventArgs e)
168
+
169
+ {
170
+
171
+ //ボタン0では、
172
+
173
+ //AppDomain.GetCurrentThreadIDをメインスレッドから呼ぶ。
174
+
175
+ int threadID = AppDomain.GetCurrentThreadId();
176
+
177
+
178
+
179
+ //メッセージボックスを使って表示
180
+
181
+ MessageBox.Show("ボタン0 ThreadID=" + threadID);
182
+
183
+ }
184
+
185
+ ```
186
+
187
+
188
+
189
+ 結果は以下のようになりました。
190
+
191
+ ![イメージ説明](d8a11cc6123a66e3de482a1d8e7e022b.png) ![イメージ説明](23069d417027f94d11f6a603c03c8314.png) ![イメージ説明](116465f0b4928560d15a06338f47241f.png)
192
+
193
+
194
+
195
+ ボタン0を押したときに使用しているAppDomain.GetCurrentThreadIDはObsoleteのようです。
196
+
197
+ よって、ボタン0を処理しているときのスレッドIDを念のためデバッガ上の"スレッド"の表示においても確認しています。
198
+
199
+
200
+
201
+ ![イメージ説明](2ec0642fbb12c16aa20e82690bbe7e78.png)
202
+
203
+
204
+
205
+ 結果として、以下3つが全て同一のスレッドIDを示すことが確認できました。
206
+
207
+ ・メインスレッドのスレッドID
208
+
209
+ ・メインスレッドから呼び出されたCOMのGetThreadIDが返すスレッドID
210
+
211
+ ・別スレッドから呼び出されたCOMのGetThreadIDが返すスレッドID
212
+
213
+
214
+
215
+ つまりは、Taskを使用して別スレッドから呼び出されたCOMのGetThreadIDは確実にメインスレッドのメッセージループで実行されているようなので、Button2内のWait()でデッドロックしない理由については未だ納得できない状態です。