teratail header banner
teratail header banner
質問するログイン新規登録

質問編集履歴

6

誤字修正

2016/09/20 18:42

投稿

MagoCat
MagoCat

スコア86

title CHANGED
File without changes
body CHANGED
@@ -184,7 +184,7 @@
184
184
 
185
185
  また、このCOMを利用するWindowsForms側のButton2クリック時の処理を以下のように変更しました。
186
186
 
187
- ```ここに言語を入力
187
+ ```c#
188
188
  private void button2_Click(object sender, EventArgs e)
189
189
  {
190
190
  //ボタン2では、GetThreadIDをTask内(スレッドプール)で呼ぶ。

5

誤字修正

2016/09/20 18:42

投稿

MagoCat
MagoCat

スコア86

title CHANGED
File without changes
body CHANGED
@@ -232,4 +232,4 @@
232
232
  つまりこの実験から何が分かるのかというと、```task.Wait();```による待ちでスレッドの処理が完全にブロックされるのではなくて、別スレッドからSTA-COMメソッドを呼び出したときに発生するメッセージは特別扱いで処理できるようだということです。
233
233
 
234
234
 
235
- 追伸:他の方からコメントで頂いたURLはまだ読んでいないので、今日明日のうちに目を通してみます。
235
+ 追伸:皆さんからコメントで頂いたURLはまだ読んでいないので、今日明日のうちに目を通してみます。

4

誤字修正

2016/09/20 18:10

投稿

MagoCat
MagoCat

スコア86

title CHANGED
File without changes
body CHANGED
@@ -159,7 +159,7 @@
159
159
 
160
160
  [追記2]の実験に幾つかの改良を加えました。
161
161
 
162
- - メインスレッドのWait()到達よりもタスク内でのmyAtl.GetThreadID()実行が確実に遅れるように、タスク内の最初で数秒スリープするようにした。
162
+ - メインスレッドの```task.Wait();```到達よりもタスク内での```myAtl.GetThreadID();```実行が確実に遅れるように、タスク内の最初で数秒スリープするようにした。
163
163
 
164
164
  - デバッグ出力文字列にスレッドIDを添えるようにした。
165
165
 
@@ -167,7 +167,7 @@
167
167
 
168
168
 
169
169
 
170
- 具体的には、まずCMyATLTestClassのGetThreadIDの中身を以下のように変更しました。
170
+ 具体的には、まずCMyATLTestClassのGetThreadID()の中身を以下のように変更しました。
171
171
 
172
172
  ```c++
173
173
  STDMETHODIMP CMyATLTestClass::GetThreadID(LONG* id)

3

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

2016/09/20 18:07

投稿

MagoCat
MagoCat

スコア86

title CHANGED
File without changes
body CHANGED
@@ -149,4 +149,87 @@
149
149
 
150
150
  かといって```task.Wait();```によるメインスレッドのブロッキング中に、COMのメッセージだけは選択的に処理されるという動作が自然かというと私は結構不自然だと思いました。
151
151
 
152
- この挙動を説明するMSDNの記述を見つけたいところですね。
152
+ この挙動を説明するMSDNの記述を見つけたいところですね。
153
+
154
+
155
+
156
+
157
+ ---
158
+ **【追記3】**
159
+
160
+ [追記2]の実験に幾つかの改良を加えました。
161
+
162
+ - メインスレッドのWait()到達よりもタスク内でのmyAtl.GetThreadID()実行が確実に遅れるように、タスク内の最初で数秒スリープするようにした。
163
+
164
+ - デバッグ出力文字列にスレッドIDを添えるようにした。
165
+
166
+ - COMメソッド内部でもデバッグ文字列を出力するようにした。
167
+
168
+
169
+
170
+ 具体的には、まずCMyATLTestClassのGetThreadIDの中身を以下のように変更しました。
171
+
172
+ ```c++
173
+ STDMETHODIMP CMyATLTestClass::GetThreadID(LONG* id)
174
+ {
175
+ *id = GetCurrentThreadId();
176
+
177
+ CString str;
178
+ str.Format(_T("InGetThreadID ThreadID=%d\n"), *id);
179
+ OutputDebugString(str);
180
+
181
+ return S_OK;
182
+ }
183
+ ```
184
+
185
+ また、このCOMを利用するWindowsForms側のButton2クリック時の処理を以下のように変更しました。
186
+
187
+ ```ここに言語を入力
188
+ private void button2_Click(object sender, EventArgs e)
189
+ {
190
+ //ボタン2では、GetThreadIDをTask内(スレッドプール)で呼ぶ。
191
+
192
+ int threadID = 0;
193
+ Task task = Task.Run(() =>
194
+ {
195
+ //3秒待機すれば、
196
+ //メインスレッドは間違いなくWait()に突入しているだろう。
197
+ Thread.Sleep(3000);
198
+
199
+ Trace.WriteLine("TaskStart ThreadID=" + AppDomain.GetCurrentThreadId());
200
+ myAtl.GetThreadID(out threadID);
201
+ Trace.WriteLine("TaskEnd ThreadID=" + AppDomain.GetCurrentThreadId());
202
+ });
203
+
204
+ //タスクの完了を待機してから、メッセージボックスを使って表示
205
+ Trace.WriteLine("BeforeWait ThreadID=" + AppDomain.GetCurrentThreadId());
206
+ task.Wait();
207
+ Trace.WriteLine("AfterWait ThreadID=" + AppDomain.GetCurrentThreadId());
208
+ MessageBox.Show("ボタン2 ThreadID=" + threadID);
209
+ }
210
+ ```
211
+
212
+ Button2をクリックすることに依るデバッグ出力の結果は以下のように成りました。
213
+ (折角なので、紹介して頂いたDebugViewを使用しています)
214
+
215
+ ![イメージ説明](a65210e5e2e1e1bdb9ec83018d8ec401.png)
216
+
217
+ 順序はこうです。
218
+
219
+ **1. "BeforeWait"がメインスレッド(ID=6128)で出力された。**
220
+
221
+ **2. "TaskStart"が別スレッド(ID=7964)で出力された。**
222
+ (タスクの最初に3秒スリープしたことで、1と2の出力には大きな時間差があります。この間にメインスレッドは確実に```task.Wait();```に突入しているでしょう。)
223
+
224
+ **3. "InGetThreadID"がメインスレッド(ID=6128)で出力された。**
225
+ (メインスレッドは```task.Wait();```でブロックされているはずなのにメインスレッドでCOMのメソッドが実行されています)
226
+
227
+ **4. "TaskEnd"が別スレッド(ID=7964)で出力された。**
228
+
229
+ **5. "AfterWait"がメインスレッド(ID=6128)で出力された。**
230
+ (このタイミングでようやくメインスレッドのブロッキングは解除されています)
231
+
232
+ つまりこの実験から何が分かるのかというと、```task.Wait();```による待ちでスレッドの処理が完全にブロックされるのではなくて、別スレッドからSTA-COMメソッドを呼び出したときに発生するメッセージは特別扱いで処理できるようだということです。
233
+
234
+
235
+ 追伸:他の方からコメントで頂いたURLはまだ読んでいないので、今日明日のうちに目を通してみます。

2

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

2016/09/20 18:04

投稿

MagoCat
MagoCat

スコア86

title CHANGED
File without changes
body CHANGED
@@ -105,4 +105,48 @@
105
105
  ・メインスレッドから呼び出されたCOMのGetThreadIDが返すスレッドID
106
106
  ・別スレッドから呼び出されたCOMのGetThreadIDが返すスレッドID
107
107
 
108
- つまりは、Taskを使用して別スレッドから呼び出されたCOMのGetThreadIDは確実にメインスレッドのメッセージループで実行されているようなので、Button2内のWait()でデッドロックしない理由については未だ納得できない状態です。
108
+ つまりは、Taskを使用して別スレッドから呼び出されたCOMのGetThreadIDは確実にメインスレッドのメッセージループで実行されているようなので、Button2内のWait()でデッドロックしない理由については未だ納得できない状態です。
109
+
110
+
111
+
112
+
113
+ ---
114
+ **【追記2】**
115
+ 「もしかするとTask.Waitでメインスレッドが完全にブロッキングされる訳ではなくCOMメッセージの処理に限っては継続されるのではないか」という旨の指摘をいただきましたので、Trace.WriteLineを用いて処理順序を追ってみました。
116
+
117
+ 具体的には、ボタン2クリック時の処理にTrace.WriteLineを何行か追加して、次のようにしました。
118
+
119
+ ```C#
120
+ private void button2_Click(object sender, EventArgs e)
121
+ {
122
+ //ボタン2では、GetThreadIDをTask内(スレッドプール)で呼ぶ。
123
+ int threadID = 0;
124
+ Task task = Task.Run(() =>
125
+ {
126
+ Trace.WriteLine("TaskStart");
127
+ myAtl.GetThreadID(out threadID);
128
+ Trace.WriteLine("TaskEnd");
129
+ });
130
+
131
+ //タスクの完了を待機してから、メッセージボックスを使って表示
132
+ Trace.WriteLine("BeforeWait");
133
+ task.Wait();
134
+ Trace.WriteLine("AfterWait");
135
+ MessageBox.Show("ボタン2 ThreadID=" + threadID);
136
+ }
137
+ ```
138
+
139
+ デバッグ実行してボタン2をクリックしたときの出力画面の様子は以下のとおりです。
140
+
141
+ ![イメージ説明](a71da1e055cc7fb8a59b51420c559649.png)
142
+
143
+ TaskStart->BeforeWait->TaskEnd->AfterWait の順で処理が進行したようです。
144
+
145
+ "BeforeWait"が出力された直後には```task.Wait();```が行われてメインスレッドがブロッキングされている。
146
+ にも関わらず、メインスレッドのメッセージループで実行されるべき```myAtl.GetThreadID(out threadID);```を含むタスクが無事終了していることが "TaskEnd" の出力から分かります。
147
+
148
+ ここで、```myAtl.GetThreadID(out threadID);```の行が```task.Wait();```よりも前に処理されたか```task.Wait();```の中で処理されたかですが、中で処理されたと考えるほうがありえそうに思います。なぜなら```task.Wait();```よりも前には他のメッセージの割り込みを許しそうなコードは一切ないからです。
149
+
150
+ かといって```task.Wait();```によるメインスレッドのブロッキング中に、COMのメッセージだけは選択的に処理されるという動作が自然かというと私は結構不自然だと思いました。
151
+
152
+ この挙動を説明するMSDNの記述を見つけたいところですね。

1

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

2016/09/19 20:37

投稿

MagoCat
MagoCat

スコア86

title CHANGED
File without changes
body CHANGED
@@ -70,4 +70,39 @@
70
70
 
71
71
  ###助けて欲しいこと
72
72
 
73
- 考察の穴を解消してください。
73
+ 考察の穴を解消してください。
74
+
75
+
76
+
77
+
78
+ ---
79
+ **【追記】**
80
+ 「もしかするとCOMオブジェクト生成時に別スレッドが起動されその中でメッセージループが回っているからデッドロックが回避されているのではないか」との指摘がありましたので、メインスレッドのスレッドIDを(COMを経由せず)表示するコードをForm1に追加してもう一度実験を行いました。
81
+ 追加したコードは下記のとおりです。
82
+
83
+ ```C#
84
+ private void button0_Click(object sender, EventArgs e)
85
+ {
86
+ //ボタン0では、
87
+ //AppDomain.GetCurrentThreadIDをメインスレッドから呼ぶ。
88
+ int threadID = AppDomain.GetCurrentThreadId();
89
+
90
+ //メッセージボックスを使って表示
91
+ MessageBox.Show("ボタン0 ThreadID=" + threadID);
92
+ }
93
+ ```
94
+
95
+ 結果は以下のようになりました。
96
+ ![イメージ説明](d8a11cc6123a66e3de482a1d8e7e022b.png) ![イメージ説明](23069d417027f94d11f6a603c03c8314.png) ![イメージ説明](116465f0b4928560d15a06338f47241f.png)
97
+
98
+ ボタン0を押したときに使用しているAppDomain.GetCurrentThreadIDはObsoleteのようです。
99
+ よって、ボタン0を処理しているときのスレッドIDを念のためデバッガ上の"スレッド"の表示においても確認しています。
100
+
101
+ ![イメージ説明](2ec0642fbb12c16aa20e82690bbe7e78.png)
102
+
103
+ 結果として、以下3つが全て同一のスレッドIDを示すことが確認できました。
104
+ ・メインスレッドのスレッドID
105
+ ・メインスレッドから呼び出されたCOMのGetThreadIDが返すスレッドID
106
+ ・別スレッドから呼び出されたCOMのGetThreadIDが返すスレッドID
107
+
108
+ つまりは、Taskを使用して別スレッドから呼び出されたCOMのGetThreadIDは確実にメインスレッドのメッセージループで実行されているようなので、Button2内のWait()でデッドロックしない理由については未だ納得できない状態です。