ダイアログアプリを作成していますが、
ダイアログのインスタンスを他のスレッドで操作してはダメという話をよく聞きますが
理由を教えていただけないでしょうか?
また例えば、ダイアログの画面操作を伴う、少し時間のかかる処理をダイアログ・クラ
ス内で行いたい場合、
(検索で検索中ファイルパスを随時表示させるなど)
以下のソースのようにハンドルをスレッドに渡してからCWnd::FromHandle()でCWndに変
換する必要があるのでしょうか?
本来なら、PostMessageでメッセージをメインスレッドに送る方法が良いのかも知れませ
んが、
ダイアログ操作が多いため、ワークスレッドでダイアログを直接操作することにしてい
ます。
class TestDlg : public CDialog {
//省略
//ボタン処理
OnBnClickeTest()
{ //これはダメ?
//AfxBeginThread( TestDlg::TreadProcStub_this, this );
//こっちを使う?
AfxBeginThread( TestDlg::TreadProcStub_hwnd, GetSafeHwnd() );
}
//スタブ ダイアログ
static UINT TreadProcStub_hwnd(LPVOID pParam)
{
HWND hWnd = (HWND)pParam;
TestDlg* pObj = (TestDlg*)CWnd::FromHandle(hWnd);
pObj->TreadProc();
return 0;
}
//スタブ ハンドル
static UINT TreadProcStub_this(LPVOID pParam)
{
TestDlg* pObj = (TestDlg*)pParam;
pObj->TreadProc();
return 0;
}
//ワークスレッド
bool TreadProc()
{
//時間のかかる処理
//ダイアログ操作!!
return true;
}
};
ダイアログでなくウインドウ全般の問題
別スレッドからの操作でもウインドウは作成したスレッドで動くのが基本
そのためウインドウを作成したスレッドでワーカースレッドを待つとデッドロックする
thisのほうがイイ
一時オブジェクトをダウンキャストなんてまずい
ハンドルなら安全みたいな説明があるがあれは盲目に過信するとまずい
携帯なので詳しいことは省略
俺ならダイアログからタイマーで定期的に結果を再表示する
結果データは排他制御しとく
>>あさん
ありがとうございます。
もともと、ダイアログクラスのthisポインタを渡して作成していたのですが、
ネットで、スレッド間でダイアログ・インスタンスの同時操作は良くないとの記述をみ
たので、ハンドルを渡すアタッチする方法に変更しました。
ただ、昨日ここに書き込みをしてから気づいたのですが、
実際に今作成しているのは、ワーカクスレッドで、ログファイルを読み込み
それを、ツリーコントロール(CTreeCtrl)に挿入していく処理になります。
(ログの量が多ければ時間がかかるのでスレッドでの処理を行います)
今までの通り、ダイアログのthisポインタをスレッド側に渡し、ツリーコントロール
を操作する場合は問題なかったのですが、
ハンドルを渡し、スレッド側でアタッチした場合は、ツリーコントロール(メンバ変
数)のハンドルがNULLになり、落ちてしまう現象が起こりました。
アタッチを行った場合、メンバ変数のCWndクラスのハンドルはNULLになるものなのでし
ょうか?
詳しいことは知りませんがこの辺に説明があります。
http://msdn.microsoft.com/ja-jp/library/h14y172e(VS.80).aspx
内部的にTLS(スレッドローカルストレージ)を使用しているようなので
ハンドル渡しの方が安全そうな気はしますが。。。
逆にローカルストレージだからダウンキャストしたら別のCWndポインターを無理に使うことになるからメンバー変数がでたらめになるんだよ
あの説明は今回のように絶対間違えた修正をする人がでるから消えて欲しい
thisを渡すことは問題ない
スレッド間は同じ空間なので別スレッドの変数にアクセスできる
たとえ別スレッドのローカルストレージだってポインターを渡せば問題なく使える
問題の根本は別にあるのに
>逆にローカルストレージだからダウンキャストしたら別のCWndポインターを無理に使う
ことになるからメンバー変数がでたらめになるんだよ
ソース見てなかったけどキャストしている部分は確かに無茶ですね。。。
ハンドル渡しでMFCクラスを使わない方が安全そう・・・ということで。
> ネットで、スレッド間でダイアログ・インスタンスの同時操作は良くないとの記述をみ
> たので、ハンドルを渡すアタッチする方法に変更しました。
CWnd* であるか HWND であるかはあんま関係無い。HWND であろうとも「スレッド間でダ
イアログ・インスタンスの同時操作は良くない」は当てはまる。
Window てのは特別でプログラマが関知しないタイミングでも処理が走る。具体例を言う
とメッセージループ関連。例えば WM_PAINT とかはプログラマが処理実行を命令している
ワケでも無いのに必要に応じて勝手に走る。
> 同時操作は良くない
例えばボタンコントロールが WM_PAINT を処理してボタン名を描画している真最中に
SetWindowText(wnd, 新ボタン名);
なんて事をされたらどうなるかって事。つまり基本は排他制御の問題。
通常ケースでは処理の実行を行うのは基本プログラマのみ。そうなると排他制御もまぁ難
しくない。だが Window はシステムからの要請(ウィンドウメッセージ)によっても(プ
ログラマの知らないトコで)処理を走らせる。これらを完璧に排他制御しようとすると
まぁメンドイ。
> PostMessage
なので Window に関しては、排他制御とかして無理やり割り込むなんて事はせずに
PostMessage() でキューに積んで「暇できたらコレやっといて」と頼むのが楽ってお話。
逆言えば完璧に排他制御できてるならば SendMessage() とかしちゃっても問題無い、の
かなと俺は思ってもいる。
> HWND hWnd = (HWND)pParam;
> TestDlg* pObj = (TestDlg*)CWnd::FromHandle(hWnd);
FromHandle() で得られるハンドルはスレッド単位で管理されている。なので
「hWnd は TestDlg のハンドルなんで TestDlg くれ」
と、本来の TestDlg を管理していないスレッドで頼んでも
「hWnd なんて登録されてねえ。代替で一時的な CWnd を用意してやったからコレ使え」
となる。
この一時的な CWnd は当然元の TestDlg なんかとは全くの別物なので
pObj->TestDlg独自変数 = 値;
なんて事をやろうものなら「Access violation!」て怒鳴られかねない類の代物。
>>皆様
いろいろと教えていただきありがとうございます。
>>gak
詳しく解説していただき、とても分かりやすかったです。
結論は、Windowsではプログラマの意図していない箇所で、メッセージ処理が、
行われる可能性があるので、サブスレッドでのCWnd関係のインスタンスを直接操作
することは、相当危険である。サブスレッドで操作して良いのはメッセージ処理に依存
しない
通常の変数、インスタンスのみ(ダイアログ・インスタンスの)ということでよろしいで
しょうか?
どうしても、スレッド側からダイアログ操作を行いたいときは、正しくPostMessage()
で順番待ちを行うということですね。
例えば、極端なケースで1回のUI処理で数千から数万回、PostMessage()を呼ぶというこ
とは、
通常、プログラム的にはよろしいのでしょうか?
上記に書いたとおり、ログデータをスレッドで読み込み、ツリーコントロールに
挿入を行うことを実現したいのですが、1行(アイテム) 1メッセージで考えた場合、
最悪なケースとして、数千回PostMessage()を呼ぶ可能性があります。
>gakさん
>例えばボタンコントロールが WM_PAINT を処理してボタン名を描画している真最中に
> SetWindowText(wnd, 新ボタン名);
>なんて事をされたらどうなるかって事。つまり基本は排他制御の問題。
処理によっては排他制御も確かに必要ですが SetWindowText は
WM_SETTEXT を送ってテキストを設定するので
この例はあまりよくないかもしれません。
でも基本はウインドウへのアクセスがメインスレッド側で同期的に行われるように
SendMessage/PostMessage だけを使うべきでしょうね。
ただし、あさんも書かれていますがSendMessage
(もしくは内部でSendMessageを使用する関数)はデッドロックに注意です。
>シンヤさん
キューがいっぱいになることを心配しているのであれば
Sleepで別スレッドに処理を譲ってはどうでしょうか。
>>subaruさん
心配点は2点です。
subaruさんが書かれた。メッセージのロスが発生しないかということと。
もう一点は、頻繁にPostMessageを呼ぶこと自体、処理が重くならないかということで
す。 コーディングの常識的にもこういうことをしてもいいのかと、ふと思いました
(^^;
ホントこの
http://msdn.microsoft.com/ja-jp/library/h14y172e(VS.80).aspx
は判らん。
ダイアログベースでのダイアログの場合は
CHogeApp::InitInstanceのローカル変数だから
このthisはTLSじゃないんだよね。
このthisはプライマリスレッドのスタックにあるメモリなんだよね。
# このthisって
他にも気になる点はあるけど略
>上記に書いたとおり、ログデータをスレッドで読み込み、ツリーコントロールに
>挿入を行うことを実現したいのですが、1行(アイテム) 1メッセージで考えた場合、
>最悪なケースとして、数千回PostMessage()を呼ぶ可能性があります。
ログデータを読んだ都度PostMessage()呼ぶ、ツリーコントロールを持つスレッドは
メッセージを受け取る都度自身のログデータを更新させる。
1行アイテム変えるときは更新したメッセージを読込むようにすれば回数を減らすこと
が出来ると思うのですがどうですか?
おかしいですね
>1行アイテム変えるときは更新したメッセージを読込むようにすれば回数を減らすこと
>が出来ると思うのですがどうですか?
訂正です
1行アイテム変えるときは更新したログデータを使うようにすれば回数を減らすこと
が出来ると思うのですがどうですか?