環境:MFC、VS2008
スレッドが動いている画面で、タイトルバーを右クリックしたままにすると
スレッドが終了せず止まったままになってしまいます。
画面はリソースでシステムメニューを無効にしているため、
右クリックは反応しないはずだと思っていたのですがこれは何故でしょうか?
以下のような、処理が完了したら自動でダイアログを閉じるような
ものを作成しています。
※右クリックしたままだと、画面が閉じずに止まったままになってしまいます。
void CMainDlg::BtnClk()
{
CThreadDlg dlg;
dlg.DoModal();
}
------------------
BOOL CThreadDlg::OnInitDialog()
{
m_thread = AfxBeginThread(WorkThread, this, 0, 0, CREATE_SUSPENDED, 0);
m_thread->m_bAutoDlete = FALSE;
m_thread->ResumeThread();
}
UINT CThreadDlg::WorkThread(LPVOID pParam)
{
CThreadDlg *pDlg = (CThreadDlg*)pParam;
Sleep(10000); //処理の代わり
pDlg->PostMessage(WM_COMMAND, IDOK, 0);
return 0;
}
おそらく右クリック中にメッセージループが回る仕様になっているんでしょうね。
そして、このメッセージループはマウスメッセージだけを処理していて、
クリックが解放されたら終了する、という仕様になっていると思われます。
ですからクリック中にメッセージをポストしても無視されてしまうのだと思います。
特にメッセージループが回るような仕様にはしていないのですが、
これはプロジェクトを作成した際に自動でそのような仕様になるのでしょうか?
また、この場合の回避方法はどのようなものがありますか?
自分が思いつくのでは、右クリックイベントを無効にするしか思いつかないのです
が・・・
結局のところ何がしたいのか書いてないのでよくわからない・・・
・右クリック中であっても終了したい
・右クリックを離したら終了したい
どっちだろう。
PostMessage でなくて EndDialog を使うべきなんぢゃないの?
こんにちわ。
・PostMessage の戻り値は?
・SendMessage に変えたらどうなる?
・IDOK を送信するのではなくて、OnInitDialog の最後で、下みたくやる方が正しいような
::WaitForSingleObject( m_thread, ... );
OnOK(); // or EndDialog
WaitFor... の使い方は忘れてるので正しいのかは不明ですが、
非同期処理の終了待ち合わせとして考えた方がよくないですか。
もともとの問題は、ダイアログにイベントメッセージを送信しすぎて
メッセージキューがあふれてしまっているのではないかとか思ったのだけど違うかな。
いや、WorkThread()はワーカースレッドですから、基本、GUIメッセージは
メインスレッドに対して飛んできます。
そして、右クリックしたままということはマウスメッセージが発生しつづ
けるということです。
メインスレッド内でメッセージが溜まっている状態ですね。
そして、メッセージの優先順位はマウスメッセージは最優先ですから、
> pDlg->PostMessage(WM_COMMAND, IDOK, 0);
は後回しと。
後回しにされたくなければ PostMessage()でなく、SendMessage()を使いましょう。
解決したのでしょか。実験してみると、
1.pDlg->PostMessage(WM_COMMAND, IDOK, 0);
は、メッセージのプライオリティが低いため、
右ボタンを離すまで、メッセージの実行順位が回ってきませんね。
2.pDlg->SendMessage(WM_COMMAND, IDOK, 0);
普通に動きます。これが良いと思います。
3.pDlg->EndDialog( IDOK);
これも動作はしますが、スレッド越しのMFCの使用ということになりますので
気持ちとしては避けたい・・・ですかね。
やるなら、
::EndDialog(pDlg->m_hWnd, IDOK);
のほうが良いかも。
以上です。
(捕捉)
pDlg->SendMessage(WM_COMMAND, IDOK, 0);
の場合、OnOK(){・・・}の処理中は、当該スレッドは
一時停止されてますので、スレッドのインスタンスを
削除する手段がなくなります。
従って、
m_thread->m_bAutoDelete= TRUE;//又はこの行の削除
が正しいコードだと考えられます。
みなさまありがとうございます。
理由も理解できましたし、回避策もばっちりです。
PostMessageはプライオリティが低いため、
処理が止まっていたんですね。
こちらでも、SendMessageを使用することで希望通りの動作ができました。
※タイトルバー右クリックしまままでもスレッドが止まらない
仲澤さんの補足もありがとうございます。
補足が無ければまた質問するところでした。
> そして、メッセージの優先順位はマウスメッセージは最優先ですから、
>> pDlg->PostMessage(WM_COMMAND, IDOK, 0);
>は後回しと。
Windowsの動作はいつから変更されたのでしょうか。
AdvancedWindows 改訂第3版だと、
PostMessageはInputEventより優先されるように書かれていますし、
TranslateMessageはこの動作を利用して、
WM_KEYxxxをWM_CHARに変換してPostしているはずです。
ロマさん。
メッセージの優先度をやや簡単に説明すると、
まず、一般に発行されたメッセージは「メッセージキュー」に
エントリされるのですが、いくつかの例外とルールがあります。
1.SendMessage()等、いくつかの特定の方法で発行されたメッセージは
「メッセージキュー」にエントリされず、直接コールバック関数に
渡され、実行されます。全てのメッセージキューのエントリをすっ飛ばして
最優先で処理されるわけですね。SendMessage()の発行元と受領先の
スレッドが異なる場合は、元側のスレッドは一時停止して、
SendMessage()の結果待ちを行います。
2.PostMessage()等で発行されたメッセージは「メッセージキュー」に
エントリされます。「メッセージキュー」は単なるFIFOではなく、
エントリされたメッセージを並べ替えます。このときの優先は
「ユーザー操作」ですね。これがロマさんの発言の部分です。
3.さらに、優先順位の低い、又は最後であることが重要となる
いくつかのメッセージは結合されて一つになったり、
後回しになったりします。WM_PAINTなどですね。
番号の若い方が、より優先度の高いものとなります。
(参考)
http://msdn.microsoft.com/en-us/library/windows/desktop/ms644927(v=vs.85).aspx
もう解決しているようですが、私が言いたかったのは
ウィンドウズがメッセージループを回す仕様にしていると言うことです。
例えば次のようなコードでは、
#define ID_TIMER 1000
BOOL CTestDialog::OnInitDialog()
{
CDialog::OnInitDialog();
SetTimer(ID_TIMER, 1000, NULL);
return TRUE;
}
void CTestDialog::OnNcRButtonDown(UINT nHitTest, CPoint point)
{
TRACE(Test1\n);
CDialog::OnNcRButtonDown(nHitTest, point);
TRACE(Test2\n);
}
void CTestDialog::OnTimer(UINT nIDEvent)
{
if (nIDEvent == ID_TIMER)
TRACE(WM_TIMER\n);
CDialog::OnTimer(nIDEvent);
}
このダイアログのキャプションで右クリックダウンすると Test1 が出力されます。
さらに WM_TIMER の出力が停止します。
そして Test2 は右クリックアップするまで出力されません。
これは CDialog::OnNcRButtonDown() の中でメッセージループが回っていて、
そのメッセージループではマウスメッセージしか処理していないからだと推測できます。
メッセージの優先度はこの件では関係ない気がします。
単にマイクロソフトが、
MSG msg;
while (::GetMessage(&msg, hWnd, WM_MOUSEFIRST, WM_MOUSELAST) < 0)
{
if (msg.message == WM_NCRBUTTONUP)
break;
// マウス処理
}
としてるからいつまで経っても WM_COMMAND が処理されないだけだと思います。
INFO: Window Message Priorities( http://support.microsoft.com/kb/96006)から引用
(MORE INFORMATIONの先頭部分です)
For example, PostMessage() puts a message in the application queue. However,
when the user moves the mouse or presses a key, these messages are placed on
another queue (the system queue in Windows 3.1; a private, per- thread input
queue in Win32).
GetMessage() and its siblings do not look at the user input queue until the
application queue is empty.
ところで、ぽんさんの最初のコードでPostMessageをSendMessageに変えると、
SendMessage発効後にm_threadはSendMessageの戻りを待っているが、
プロセスはWinMainを抜けて終了してしまうということになりませんか。
訂正 すみません、
> void CMainDlg::BtnClk()
> {
> CThreadDlg dlg;
> dlg.DoModal();
>}
ここを読んでいませんでした。私の前投稿の後半を取り消します。