ウィンドウメッセージの滞留?

[戻る]
NEO 2011/01/19(水) 23:25:27
以下のようなMDIアプリケーションがあります。

MDIフレーム@
MDIフレームA(MDIフレーム@のフォームビューのクライアント領域がダブルクリックされ
ると表示)

■MDIフレームAの表示方法
(1) CreateNewFrame()
(2) (1)で作成したフレームでInitialUpdateFrame()する

■MDIフレームAがすでに表示されている場合は、閉じるまで1秒間隔でSleep()しながら
待って表示。

■MDIフレームAを閉じる仕組み
(1)MDIフレームAに対してウィンドウメッセージをSendMessage()する。
(2)MDIフレームAはウィンドウメッセージを受けると、Activeなフォームビュークラスに
WM_CLOSEをSendMessage()する。
(3)フォームビュークラスのOnClose()で終了処理用のスレッドを起動し、モーダルダイア
ログを表示。ダイアログにはスレッドハンドルを渡す。
(4)モーダルダイアログでは、フォームビュークラスの終了処理用スレッドの終了を150ms
間隔のTIMERイベントで監視し、スレッドが終了するとEndDialog()する。

クライアント領域のダブルクリックを連続で行うと、モーダルダイアログにTIMERイベン
トが発生しなくなってしまい、
アプリケーション自体が応答なしになってしまうことがあるので困っています。
TIMERイベントが発生しないどころか、モーダルダイアログのダイアログプロシージャが
呼ばれなくなってしまいます。
モーダルダイアログのオーナーウィンドウハンドルには、MDIフレームAのフォームビュー
クラスのウィンドウハンドルを指定し、インスタンスハンドルにはMDIフレームA表示元
MFC拡張DLLのDllMain()の第1引数を指定しています。

ここまでで何か原因になりそうなことがあるでしょうか?
また、調べた方が良さそうなことがあれば教えてください。

Ban 2011/01/20(木) 07:51:37
MDIフレーム(1)とMDIフレーム(2)が同じスレッドに帰属してそうにみえます。

Sleepするとその間そのスレッド全体のメッセージ処理が止まるので、
MDIフレーム(2)もダイアログもメッセージが処理できなくなります。

終了処理だけは別スレッドのようなのでメインスレッドがSleepしても動きますが、
ウィンドウは何も考えずにMFCで作成するとみんなメインスレッドで動くので、
多分そのせいじゃないでしょうか。


UIスレッドを別に作ってそこでMDIフレーム(2)を動かせばとりあえず直りそうですが…




# ○付の数字は避けて()に置換えています。

PATIO 2011/01/20(木) 09:57:23
基本的にウインドウメッセージとタイマーを使って
制御を使用としているならSleepは使わないと思います。
理由に関しては既にBANさんが書かれている通りですね。

タイマーイベントの間隔とかその辺の調整で
待ち時間を調整するとかしないとうまく行かないのでは。
UIスレッドでマルチスレッド化するよりはそっちの方が
調整は簡単そうです。

基本的にMFCを使わなかったとしても意識してマルチスレッド化
しようとしないければ、全てのウインドウはメインスレッドで
動いている状態になると思うので安易にSleepは使えませんね。
他のウインドウの動作にも影響してしまいますから。

hirocco 2011/01/20(木) 11:09:55
ふむふむ、SleepではなくDoEvents的な処理で待機するといいのかな?
それとも、終了用に表示させた(しばらくお待ちください?)モーダルダイアログ内で
そのスレッドでさせる処理を実行するのかな?
そんなとこではどうでしょう

hirocco 2011/01/20(木) 11:12:38
あ、原因って, SendMessageじゃないかな?

仲澤@失業者 2011/01/20(木) 12:35:37
まぁSendMessage()が原因でしょうが、やや遠回りな仕組みですね。
そもそもCreateNewFrame()の戻り値のフレームの
ポインタを保管しておいてDestroyWindow()すればよいのでは。

m_MDI_Frame2 = CreateNewFrame(...); // 開く
  :
  :
m_MDI_Frame2->DestroyWindow(); // 閉じる
m_MDI_Frame2 = NULL;

タイマーもイベントも無用です(vv;)。

ryo 2011/01/20(木) 12:49:15
A「2のフレームを閉じるメッセージは誰がどこでどのタイミングで出すのか?」
B「1秒ごとにSleepで待つ際、たまる可能性のあるメッセージをどうしてるか?」
C「こういった流れの中で『モーダルダイアログ』で、タイマー起こして待ちをおこなっ
てる」
不安材料は多々

A
もし、
「1をダブルクリックしたとき、2がすでにあるなら、
  "一度閉じさせてから”、2を再表示」
とすると
SendMessageは、相手先での処理が終わるのを待つので(この先でモーダルダイアログな
のか?)
1から終了の指示を「SendMessage」で送信しつつ、Sleepで終了を待つ
という流れに疑問。

B
たとえばこんなの
ttp://www.softist.com/programming/doevents/doevents.htm

C
アクティブなウィンドウには、オリジナルの終了メッセージをなげる
ソレ受け取ったら、スレッドをたて、また、ダイアログをモードレスで作る
タイマーでスレッド終了を待つのではなく
スレッドの方から、元のウィンドウ(2番のウィンドウとかでも)に対し、
オリジナルの「スレッドが終了しました」とメッセージをPostmessageで投げる。
ソレを受け取ったら、CLOSEする

ITO 2011/01/20(木) 16:54:54
 似たようなアプリは作ったことがありますね。
 僕の場合、
  1.「CMultiDocTemplate」を使って必要分のフレームを用意する。
    2. 開くときは、リソースのテンプレート文字を検索してウインドウを開く。
  3. 閉じるときは、メインフレームからメッセージ等で知らせて、子ウインドウ
   の「DestroyWindow(); 」等で閉じる。
 ですね。
 
 

NEO 2011/01/20(木) 19:33:38
みなさん、ご意見ありがとうございます。

BANさんのおっしゃるとおりですね。
MDIフレーム(2)はMFC拡張DLLに実装してまして、MDIフレーム(1)を実装したアプリケー
ションでそのDLLを使ってもらっています。
DLLだけが私の担当で、MDIフレーム(1)を実装したアプリケーションは私の担当ではない
ので、その担当の方と調整が必要になるのですが、工数と難易度から考えると、Sleep間
隔を1秒からできる限り(10msなどに)短くしてもらうのが現実的なのでしょうか?

MDIフレーム(2)の終了処理には時間がかかることがあるので、ダイアログを表示している
のですが、DestoryWindow()を採用するにしても、MDIフレーム(2)の終了処理は必要なの
で、ダイアログを表示することになります。

> ryoさん
> A「2のフレームを閉じるメッセージは誰がどこでどのタイミングで出すのか?」
MDIフレーム(1)のフォームビューのクライアント領域ダブルクリックのイベントハンドラ
から出します。

> B「1秒ごとにSleepで待つ際、たまる可能性のあるメッセージをどうしてるか?」
特に何もしていません。

> C
>タイマーでスレッド終了を待つのではなく
>スレッドの方から、元のウィンドウ(2番のウィンドウとかでも)に対し、
>オリジナルの「スレッドが終了しました」とメッセージをPostmessageで投げる。
>ソレを受け取ったら、CLOSEする

終了処理のスレッドは、MDIフレーム(2)のフォームビューの資源解放をやっていますの
で、スレッドが終了するまではMDIフレーム(2)は破棄できません。
したがって、MDIフレーム(2)のフォームビューは終了処理用スレッドの終了を知る必要が
あるのです。
タイマーでスレッド終了を待たずに、スレッドの終了はスレッド自身で通知した方がいい
とは思っていますが、
スレッドの終了を監視して(待っている)いるのは、MDIフレーム(2)のフォームビューな
ので、そこにメッセージを投げても受けれないのではないでしょうか?

PATIO 2011/01/24(月) 22:34:03
PATIO死ね

Ban 2011/01/25(火) 06:02:23
> Sleep間隔を1秒からできる限り(10msなどに)短くしてもらうのが現実的なのでしょう
か?

Sleep時間を縮めても解決しない可能性もありそうに思えます。
(現象的に、応答無しになった場合でも時間がたてば復帰するのでしょうか。
 SendMessageから二度と復帰しなかったりとかは大丈夫ですか?)

既にあがってますが、一番安直なのはSleepの代わりにDoEvents/メッセージループでしょ
うか。


そもそもは「UIスレッドでSleep/Waitするのがイケてない」のであって、
(UIスレッド上のウィンドウハンドラ中で外部処理を待つべきではありません)
本来はMDIフレーム(1)に「(MDIフレーム(2)の)完了待ち」という状態でも
持たせてそれに遷移させ、一度ハンドラからは抜けるべきだと思いますが。

SleepでMDIフレーム(2)を待っているのが最大の問題で、
MDIフレーム(2)のスレッド待ちはそれに比べれば些事に思えます。

Ban 2011/01/25(火) 06:53:30
> スレッドの終了を監視して(待っている)いるのは、MDIフレーム(2)の
> フォームビューなので、そこにメッセージを投げても受けれないのでは
> ないでしょうか?

ちなみに、監視方法はなんですか?
スレッドハンドルのWaitForSingleObject(orMultipleObjects)ですか?
タイムアウトの引数はどうなってますか?

もしこれでMDIフレーム(2)のハンドラ内で完了待ちをしている場合、
(ブロッキングしている/ポーリングだがループなどでハンドラを抜けない等を含む)
こちらも問題/実はこちらが問題、という可能性もありそうです。

もしこうなっているなら確かにMDIフレーム(2)はメッセージを受け取れませんが、
そもそも「ウィンドウのハンドラ内で外部処理を待つ」のが(以下略


もし完了の通知が取りたいなら、例えば、スレッドの方に完了通知を投げて欲しい
HWND(ここではダイアログ?)を渡すとか?

MDIフレーム(2)を閉じる仕組みは、(終了処理がビューに帰属するなら)
1. MDIフレーム(2)に対してウィンドウメッセージをPostMessage()する。
2. MDIフレーム(2)はウィンドウメッセージを受けると、
   ActiveなフォームビュークラスにWM_CLOSEをSendMessage()する。
3. フォームビュークラスのOnClose()で終了処理用のスレッドを起動し、
   モーダルダイアログを表示。スレッドにはダイアログのHWNDを渡す。
4. モーダルダイアログではスレッドからのメッセージを待つ。
5. スレッドからモーダルダイアログにメッセージが届くとEndDialogする。
   (モーダルダイアログにウォッチドッグタイマが必要かは要件による)

# ダイアログを開いているのでメッセージは処理できてますが、
# 皆さんご指摘のようにSendされてきたOnClose内で待っているのですね、これ。

ちなみに、ここまでは基本的に「Windowsの作法としてハンドラ内で(以下略」前提で
メッセージポンプを止める奴が悪い、行儀の悪い奴を矯正する方向でのお話です。

で、逆に終了処理のスレッド化の目的がもしかして「そういう駄目な子がいても何とか
自分はちゃんと動きたい」だった場合は、ウィンドウのスレッドわけが必要かと。

何もしないと(DLLだろうが何だろうが)ウィンドウは全部同じスレッドで動いてしまい、
誰かにメッセージをブロックされてしまうとどうしようもないので、
こういう影響を避けたい(MDIフレームが片方止まっても他は止まらない)なら、
同じメッセージキューを使わないように自分用のUIスレッドを用意する方が多分安全。

# 処理内容にもよるでしょうが、MDIフレーム(2)自体が別スレッドで動作していれば、
# もしかしてMDIフレーム(2)の内部状態として終了中に遷移させることで
# 終了処理スレッド自体が不要に生るかもしれませんが。

PATIO 2011/01/25(火) 10:18:18
私なら、Sleepで待つのではなくてTIMERで待つカナと。
Sleepは文字通り、スレッドを止めてしまうので得策では無いと思います。

私が以前によくやっていたのは、TIMERをかけておいて
WM_TIMERを受け取ったら、TIMERを解除。必要なチェックを行なってから
条件が揃っていなかったら、再びTIMERをセットを繰り返します。
TIMERだと一旦制御が返るのでスレッドが止まってしまう事もありません。
必要な処理がコマ切れになる事になりますから、必要なら状態管理をして
次にWM_TIMERが来た時に続きから処理できるような仕掛けは必要ですけれど。

PATIO 2011/01/25(火) 10:20:37
TIMERだと待ち合わせ時間の精度がかなり落ちてしまうので
精度が必要な待ち合わせなら問題になるかもしれませんけれど、
今回のような状況ならTIMERの精度でも良いのではないかと思います。

NEO 2011/01/26(水) 23:06:00
SleepをTimerに変更することで対応しようと思っているのですが、UIスレッドのマルチス
レッド化する方法について、参考までに教えてください。

CWinThreadをCreateして、そのスレッドでフレームを作成(CreateNewFrame)するだけで
良いのでしょうか?

PATIO 2011/01/27(木) 19:40:00
何処までご自分で調べられましたか?

MSDNにUIスレッドの生成方法についての記述が有りますので
それを参照してみてください。

マルチスレッド UIスレッド MFC
これくらいのキーワードでググれば、でてくると思います。

NEO 2011/01/30(日) 16:17:43
CWinThreadの派生クラスをCreateして、そのスレッド内でCreateNewFrame()すれば良いの
かな・・・

NEO 2011/02/15(火) 17:46:51
UIスレッドをマルチスレッド化することになりました。

今回が初めてなのですが、既存の機能には手を加えません。
CWinThreadの派生クラスをCreateして、そのクラスの中でウィンドウを生成します。
既存機能に影響があるのでしょうか?
気をつけることがあれば教えてください。
アバウトで申し訳ありませんが、よろしくお願いいたします。

PATIO 2011/02/15(火) 17:56:33
通常のウインドウ間のやり取りとは違って
別のスレッドになりますからスレッド間でデータのやり取りを
する時にそれを意識して行う必要があるという事だと思います。
既存機能の構成等がわかりませんのでこれ以上は書きようが有りません。

同時に同じデータに対して更新が発生しうる場合等々、
一般的にマルチスレッドで気をつけるべき部分は対応が必要でしょう。
スレッド間でウインドウクラスのポインタのやり取りを行わないのも
ワーカースレッドの場合と変わらないと思います。

NEO 2011/02/15(火) 20:45:49
排他すべきところは排他しています。
UIスレッドが複数になったからといって、動かなくなるようなことはやっていないつもり
なのですが、
シングルでは動くけど、マルチでは動かないような実装方法ってあるのかと思いまして。

PATIO 2011/02/16(水) 14:35:17
UIスレッドをマルチスレッドで動かす事がないので
確実かといわれると困りますが、
排他をきちんと意識していれば、大丈夫なんじゃないかと
思います。MFCのフレームワークにしても明示的にマルチ
スレッドに非対応と無ければ大丈夫だと思いますし。

私の場合、理屈的にはユーザーが二つの画面を全く同時に
操作すると言うケースは考えにくいのでUIスレッドを
マルチスレッド化する事はほとんどないんです。
そういう意味ではNEOさんの方でやってみてわかった事を
フィードバックしていただけると助かるなぁと言うのが
本音ですね。

NEO 2011/02/16(水) 23:36:29
[[解決]]
>理屈的にはユーザーが二つの画面を全く同時に操作すると言うケースは考えにくいので

私の場合も、「二つの画面を全く同時に操作すると言うケース」ではないのですが、
ある画面から別画面を表示させるときに、表示中の画面の終了を待ってからという仕様が
ありますので、
マルチスレッド化せざるを得ないのでしょうね。

マルチスレッド化について何か役に立つことを発見したら、投稿させていただきます。

ありがとうございました。

Ban 2011/02/17(木) 00:02:21
> シングルでは動くけど、マルチでは動かないような実装方法ってあるのかと思いまし
て。

いろいろあると思いますよ。以下、一例。


> MFCのフレームワークにしても明示的にマルチ
> スレッドに非対応と無ければ大丈夫だと思いますし。

MSDNの説明自体に「スレッドに非対応」とか明示されていることは少ないと思いますが
明示のないものでも、例えばCWndをむやみに他スレッドに渡したりすると
TLSで嵌ったりすることはあるかと。
#  MSさんは、明記されていることは極力互換性を保ってくれますが、
#  そうでないことは今後の版で予告なく変わったりすることも多々ありますので、
#  保証できないことは書かれない感じではないでしょうか

 ちなみに、KBも「明示」に含まれますか。
 (http://support.microsoft.com/kb/147578/en-us)

MFCのマルチスレッドでは多分典型的な奴ですが、
SafeHwndでHWNDを渡すとか、そもそも渡さないですむようにするとか注意が必要で、
ダイやログやMFCが管理しているものも含めて、
「各ウィンドウが *どのスレッドに* 属するか」も重要かと。

ちなみに、SendMessageとか書かれていますが、MFCのCWnd::SendMessgaでしょうか。
それとも::SendMessageでしょうか。いずれにせよCWndには注意した方がいいですよ。


まぁ、色々(MFCの内部実装も含め)気をつけることが増えるので、
短納期で初めてとかいうお話で、MFCをマルチスレッドで動かすなんてのは
やらなくていいならしないが吉だとは思いますが。
# 他のワークアラウンドについては提示済みなので割愛。

Ban 2011/02/17(木) 00:06:52
# 投稿したらリロードされて解決になってました…。

検討をお祈りします。

古いスレッドには追加発言できません。