VC2008 MFC SDIのプログラムを作成しています。
ドキュメントクラスでワーカスレッドを作成し、ワーカスレッドからメインスレッドへ
通知を行いたいのですが、よい方法があるでしょうか。
補足:---------------------------------------------------------------
メインスレッドで待機処理をするとUIが固まってしまうので、
そのような状況は回避しなくてはいけません。
データ処理のためにCWndクラスからオリジナルクラスを作成して
PostMessageを使用するというのもできますが、
あまりスマートではない気がするので別の方法があれば、というところです。
---------------------------------------------------------------------
報告対象がメッセージキューを持っている場合は、
1.HWNDに対してメッセージを送付する。
2.スレッドIDに対してメッセージを送付する。
が最も単純な方法になります。
イベントなど、同期オブジェクトを使用する場合は、
そのオブジェクトのシグナル状態を監視するための
無限ループが、別途受信側に必要になります。
さて、全てのウインドウ(UI)はメッセージで駆動されています。
つまり制御の本流はメッセージキューなのであって、
他のコードの流れは全てその分流に過ぎません。
従って、本流への制御移管が「スマートではない」との
お考えは、個人的にはやや奇妙に感じられるのですが、いかがでしょう。
メインスレッドへ通知ならPostMessageでいいよ。
それで十分と思うが。
最近アプリ作ってないから忘れたが既にCwndから派生クラスって作成済みじゃなかった。
WM_APP+nの追加は最初面倒ではあるけど。
ワーカースレッドを立てるときに、
メインとなるウィンドウのハンドルをワーカースレッドに引き渡しておき
ワーカースレッドから、そのハンドルへ終了通知(自作メッセージ)をPostMessageする
メインは、この終了通知をうけたときに終了処理すればいいだけなので、
別段、待機処理をする必要はない。
(作り方によってはワーカースレッドの終了処理がいるが)
>仲澤@失業者さんへ
私の予想ですが、質問者さんは
スレッドの生成・開始・待機・終了までの一連の作業を一まとめに行う
例えば、大量の計算をスレッド化によって時間を短くするようなタイプの使い方を
勉強した段階なのではないでしょうか。
それゆえ、待機を(その場でしないといけないと思って)すると
UIが固まるということになっているのだと思います。
そこで、メッセージで最初と最後以外メインとワーカーが
関係しないような動作を実現するためにあげた
>データ処理のためにCWndクラスからオリジナルクラスを作成して
これは、メインUIとは別のメッセージループをもつ子ウィンドウをつくって、
そこで処理するという案が浮かんだが
何かスッキリしないと感じてるのだとおもいます。
回答ありがとうございます。
PostMessageやPostThreadMessageによる通知は知っていますし
それを使うことによってUIスレッドが固まることも全くありません。
ただ今回の質問の目的はドキュメントとビューの分離をおこなうため、
CWndクラスから派生させたオブジェクトを使用しないで済む方法が
あれば知りたかったのですが、どうやらないみたいですね・・・
ドキュメントクラスではPostMessageは使用できないはずですので。
例えば、RS232Cのような外部通信からデータを取得するのも
ドキュメントクラスではなくビュークラスに実装し、
取得後にドキュメントクラスに送るなどのようにしなくては
いけないのかな
自分ならPostMessageで通知するなら通知先はビューよりはフレームウィンドウかなぁ
ウィンドウに関係ない話なのにウィンドウに通知するのが気持ち悪い場合は
PostThreadMessageでメインスレッドに通知したのを
APPクラスのPreTranslateMessageをオーバーライドして捕まえればどうでしょう?
ワーカスレッド生成時に
通知先のスレッドIDをAfxGetApp()->m_nThreadID とか GetCurrentThreadID() あたりで
得て
ワーカスレッドに教えておけばいいのかな
やや、やりたいことが不明なので、
スレッドを使用するときの一般的なコードを示します。
今回はドキュメントクラスがスレッドを生成するとのことで
そんな感じにしてみます。
class MyDoc : public CDocument{
friend UINT __cdecl MyDocThreadFunc( LPVOID pParam);
protected: BOOL m_Th_Run;
// スレッド開始
protected: ThreadStart(){
::AfxBeginThread( MyDocThreadFunc, this);
}
// スレッド主処理
UINT ThreadMain()
{
m_Th_Run = TRUE;
// スレッド内処理
m_Th_Run = FALSE;
return 0x12345678; // 終了コード
}
};
UINT __cdecl MyDocThreadFunc( LPVOID pParam)
{
MyDoc * This = ( MyDoc *)ex_Param;
This->ThreadMain();
}
これで、ドキュメントのインスタンスは自身の関数 ThreadMain()で
スレッド内の処理ができるわけですが、自身の内部であれば
特に気にすることなくメンバを参照できます(場合によっては
やばいこともありますが)。
当たり前ですが、232Cだろうが、TCP/IPだろうが好きに通信しましょう。
また、ぶっ飛ばないように注意すれば、主アプリケーションであろうが、
メインフレームだろうが好きに参照してかまいません。
間違った
UINT __cdecl MyDocThreadFunc( LPVOID ex_Param)
{
MyDoc * This = ( MyDoc *)ex_Param;
return This->ThreadMain();
}
でした。
ソラで書いてるのでご容赦ください orz.
僕なら、
1. 共有データとして、グローバルデータを作成する。
2. 書き込みは必ず一箇所にする。数箇所の場合は保護する。
3. 実際に処理するデーターはクラス等を使い、保護する。
4. スレッド等は共有データを使う。
にしますがどうでしょうか?
>ただ今回の質問の目的はドキュメントとビューの分離をおこなうため、
VIEWと分離するとしても
ドキュメントを操作するなんらかのスレッドが存在するわけだから
そのスレッドにメッセージを投げればいいのでは?
>CWndクラスから派生させたオブジェクトを使用しないで済む方法が
PostThreadMessageを知ってるのなら、
なぜ、CWndからの派生にこだわる必要があるのでしょうか?
回答ありがとうございます。
皆さんの回答を参考にもう一度プログラムを見直してみます。
話はすこし変わりますが、
仲澤@失業者さんのサンプルプログラムでスレッド内処理と書かれている部分に
データ処理をさせることになるわけですが、
取得したデータをビューに反映させるためにUpdateAllViews()を
どこで入れるのがよいのでしょうか。
スレッド内では入れられないので、スレッド外で実装することに
なると考えていますが・・・・
>取得したデータをビューに反映させるためにUpdateAllViews()を
どこで入れるのがよいのでしょうか。
やってみてはどうでしょう。
つまり、スレッドの主処理から呼んでみるわけです。
UpdateAllViews()の処理内でCWndのスレッドに依存する部分
に抵触するコードがあれば、それは正しく動かないでしょう。
そうでないなら、つまりm_hWndにのみ関係するコードのみで
構成されているなら正しく動くわけです。
そして、もう一度考え直してみるわけですね。つまり、
1.CDocumentからスレッドを起動することのリスクは合理や否や
ということをですね。
当たり前ですが、自分の示したコードはS-01さんのご希望を
取り入れたものであって、目的としているシステムが
求めている合理性とは無関係です。
>そして、もう一度考え直してみるわけですね。つまり、
>1.CDocumentからスレッドを起動することのリスクは合理や否や
>ということをですね。
たしかにビュークラス(もしくはメインフレーム)でスレッドを処理するほうが
一般的かもしれませんね。
ちなみに、UpdateAllViews()をCDocumentのスレッドから呼び出すと、
アプリが落ちます。当然と言えば当然ですが。
(CDocumentにスレッドを追加したこと以外は、ビュークラスも含めて
何も手を加えていません。)
せっかくCDocumentのスレッドでデータを得ても
ビュー側に通知できないならば、ドキュメントクラスでの実装はやめ、
ビュー側で通信データを取得するようにプログラムを組んでみます。
解決チェックです。