マルチスレッドプログラミングに関しての質問ですが、MFCのワーカースレッドからメイ
ンスレッドのMFCオブジェクトにアクセスしたい時に皆さんはどうしていますか?
MSDNには「スレッドは自分自身が作成した MFC オブジェクトしかアクセスできませ
ん。」と書いてありますが、私の環境ではワーカースレッドにMFCオブジェクトのポイン
タを渡すと100%ではありませんが、けっこうまともに動作します(ソフト一つがまとも
に動く位)。
でもやはり心配なので皆さんがどうしているか興味あり質問しました。
HWNDで渡してアタッチした方が安全なのでしょうか?
環境は、XP + VC++.NET2002です。
MFCオブジェクトを別スレッドでアクセスしなくてもよいようにプログラム構造を考
えるのが本質であって、それを回避してどんな意味があるのでしょう?
将来的に必ず動作する保障はありませんので、そういった使い方をしないのが原則だと
思います。
またまた横槍ですみません。
例えばダイアログベースのアプリで長ーい処理のループがあったとします。
別スレッドでループ処理をさせてループを1回こなすごとにダイアログの
プログレスバーをカウントさせる。
スレッドにダイアログのポインタを渡してこんな事をしてはいけないのですか?
(結構普通にやってたりします。)
別スレッドにしないで処理した場合、ダイアログのプログレスバー以外の部分が
メッセージ処理できないので、真っ白(灰色)になってしまうんですよね。
(PeekMessageとかいれても、ループ1回につき1つのメッセージしか処理しないので、
同じ事になります。)
こんな時はどうすればいいのでしょう?
こういった処理の場合は、プログレスバーの%を保存する変数をふくむオブジェクトを
CDialogとは別に生成して、このクラスポインタをメインスレッドと別スレッドで
共有すればよいとおもいます。
別スレッドは値を更新したときにメインスレッドにダイアログの更新を要求する
メッセージを投げるという動作がよいのではないでしょうか。
CDialogのポインタを別スレッドに渡して参照してもエラーにはなりませんが、
別スレッドでダイアログの描画を実行すればエラーになります。
変数の参照だけを行わせるためにCDialogのポインタを別スレッドに渡すという
コーディングは小さな自分だけのプログラムなら問題ないかもしれません。
でも、10人、20人という人数でいろいろなパーツを作成するプロジェクト
ではどうなるでしょう?
作成者の一人は自分の関数が別スレッドで動作することを知らないかもしれません。
その関数にCDialogのポインタが渡され、CDialog の構成要素であるプログレスバー
のカウンタを自身の関数で更新したら、なにも考えずにダイアログの再描画
関数をそのままコーディングするのではないでしょうか?
自分が作成したプログラムも3年後に改造する場合も同じように間違う場合が
あります。
これは、本来は渡してはいけないCDialogのポインタを使ったことに起因する
不具合です。
こういった不具合の芽を残さないように作ったほうが綺麗なプログラムに
なると思います。
問題なく動けばどうでもよいと思うなら、関係ないお話です。
すみません。何度も読み返したのですが、理解が追いつきません。
ダイアログのポインタを渡して描画などをする関数を操作するのはNGで、
hWndを渡してPostMessageするのはOKという事でしょうか。
そうすべきです。と言われれば、なんとなくそうなのかなと思いますが、
何故なのかがいまいちよく分りません。
プログレスバーに関してはメッセージを投げるように変更することは出来ますが、
スレッド内ではダイアログの中止ボタンが押された時にTRUEになる変数も参照
していますし(TRUEを確認したら終了する)、そもそもダイアログのメンバである
CArrayに対する処理をしているので、参照しまくりです。(そして変更しまくり)
これらをダイアログとは別のオブジェクトにする(グローバルにする?)と何が
変化するのでしょうか。
これはデータクラスを作って管理した方が、コードが見やすくなるからと言う理由
でしょうか。(そうだとすると、勿論それは理解できますが、スレッドとはあまり
関係の無い別の話の様な気がします。)
CArray自体MFCオブジェクトですが、これは共有しても良いのでしょうか。
ふたつの話が混在しているので分けるためにMSDNを引用します
・複数のスレッドからのオブジェクトへのアクセス
サイズおよびパフォーマンス上の理由により、MFC オブジェクトのスレッドの安全性
は、オブジェクト レベルでは保証されず、クラス レベルでしか保証されません。つま
り、2 つの異なるスレッドで 2 つの異なる CString オブジェクトを操作することはで
きますが、2 つの異なるスレッドで同じ CString オブジェクトを操作することはできま
せん。複数のスレッドで 1 つのオブジェクトを操作する必要があるときは、Win32 のク
リティカル セクションなどの同期機構を使ってアクセスを保護します。クリティカル
セクションなどの関連オブジェクトについては、『Platform SDK』の
「Synchronization」を参照してください。
クラス ライブラリは内部的にクリティカル セクションを使って、デバッグ メモリの割
り当てなどが使うグローバル データ構造を保護しています。
・非 MFC スレッドからの MFC オブジェクトのアクセス
CWinThread オブジェクトを使わずにスレッドを作成したマルチスレッド アプリケーシ
ョンでは、作成したスレッドから MFC オブジェクトにアクセスできません。つまり、
MFC オブジェクトにセカンダリ スレッドからアクセスするには、セカンダリ スレッド
を「マルチスレッド : ユーザー インターフェイス スレッドの生成」または「マルチス
レッド : ワーカー スレッドの生成」で述べた方法で生成する必要があります。ほかの
方法では、マルチスレッド アプリケーションの実行に必要な内部変数をクラス ライブ
ラリで初期化できません。
つまり、CWinThread オブジェクトから派生したワーカースレッド・ユーザインタフェー
ススレッドはMFCオブジェクトを共有するようコーディングしてもエラーは出ませんし、
実行させれば、一部のクラスアクセス以外はそれなりに動きます。
でも実際は排他制御を行わないと安全ではないということです。
CArray を使用するにしてもそのデータをアクセスする前にメインスレッドとセカンダリ
スレッドでクリティカル セクションなどによる排他制御を行ったほうが良いと思いま
す。
CDialogに排他制御を入れてもデータアクセスしてもかまいませんが、新しくデータクラ
スを作ってアクセスの排他制御を行ったほうがよくないでしょうか?
>ダイアログのポインタを渡して描画などをする関数を操作するのはNGで、
>hWndを渡してPostMessageするのはOKという事でしょうか
CWinThread オブジェクトから派生したワーカースレッド・ユーザインタフェーススレッ
ドはCWnd,CDialogのポインタを渡しても動作しますが
CWnd(hwnd)にPostMessageすれば描画動作はメインスレッドだけで順番に処理され、排
他制御の必要がなくなります。
つまり、描画動作をメインスレッドにまとめることができるので綺麗になると思ってい
ます。
「すべき」というより、「このほうがよくないかい」という感じで書いています。
「えらそうに」と思われたら申し訳ないです。
はるさん、丁寧な説明ありがとうございます。
ようやく理解できました。(かな?)
つまりスレッドがCArrayを操作している最中にメインのダイアログでも
CArrayを操作しようとするとコンフリクトしてしまうから、別のオブジェクトにして
クリティカルセッションで保護した方がよい。という事でよろしいのでしょうか。
現在、スレッド実行中にそんな事する可能性は無いとしても、後から変更を加える時に
忘れてて、操作してしまうかもしれない、と。
スレッドがダイアログのポインタで描画している最中に、ダイアログ自身が自分の
都合で描画してコンフリクトしてしまうかもしれないから、メッセージを投げて、
ダイアログにメッセージキューの順番で処理させる、と。
確かに、すべてそのようです。
このような理解でよろしいでしょうか。
ありがとうございました。
ともさん、割り込んで失礼しました。
オブジェクトを渡す時には、クリティカルセクションで保護してから渡す。が、
良い方法のようです。
はるさん、ゆうさん、ご意見ありがとうございます。参考になりました。
マルチスレッドは避けては通れない道なのに、ちゃんとしたマルチスレッドプログラミ
ングの方法を解説している書籍やサイトは何故か少ないように思えます。こういう掲示
板でもっとマルチスレッドに関する話題で盛り上がれば良いですね。