VC++6.0 MFC WindowsXP sp2
DoModalで立ち上げたクラスで以下の処理を行っています。
OnInitDialogでスレッド分岐したDeleteFunc関数の中で
OnDlgEndを呼んで処理を終了させています。
その時、PostThreadMessageでスレッドを終了すると
自分自身を終了するので処理が止まってしまいます。
また、PostThreadMessageとWaitForSingleObjectをコメントにすると
スレッドが終了しないままダイアログを閉じるので
エラーになってしまいます。
なんとか上手い方法で正常終了させたいです。
DoModalで立ち上げているのが、難しさをさらにupさせています。
誰か教えてください。
XXXXDlg::XXXXDlg( CWnd* pParent /*=NULL*/)
: CDialog(XXXXDlg::IDD, pParent)
{
m_WindowText = ";
}
BOOL XXXXDlg::OnInitDialog()
{
CDialog::OnInitDialog();
m_bCancel = FALSE ;
m_thread = AfxBeginThread( DeleteFunc, this, THREAD_PRIORITY_NORMAL);
return FALSE;
}
void ProgressBarDlg::OnDlgEnd(){
// スレッドを削除
::PostThreadMessage(m_thread->m_nThreadID, WM_QUIT, 0, 0);
::WaitForSingleObject(m_thread->m_hThread, INFINITE);
CDialog::OnCancel();
}
static UINT DeleteFunc( LPVOID pParam)
{
ProgressBarDlg* pDlg = (ProgressBarDlg*)pParam;
for( int i = 0; i < FullPath.size(); i++ ){
処理
:
}
pDlg->ShowWindow(FALSE);
pDlg->OnDlgEnd();
return 0;
}
少しソースを間違えました。
これが正規のソースです。
XXXXDlg::XXXXDlg( CWnd* pParent /*=NULL*/)
: CDialog(XXXXDlg::IDD, pParent)
{
m_WindowText = ";
}
BOOL XXXXDlg::OnInitDialog()
{
CDialog::OnInitDialog();
m_bCancel = FALSE ;
m_thread = AfxBeginThread( DeleteFunc, this, THREAD_PRIORITY_NORMAL);
return FALSE;
}
void XXXXDlg::OnDlgEnd(){
// スレッドを削除
::PostThreadMessage(m_thread->m_nThreadID, WM_QUIT, 0, 0);
::WaitForSingleObject(m_thread->m_hThread, INFINITE);
CDialog::OnCancel();
}
static UINT DeleteFunc( LPVOID pParam)
{
XXXXDlg* pDlg = (XXXXDlg*)pParam;
for( int i = 0; i < FullPath.size(); i++ ){
処理
:
}
pDlg->ShowWindow(FALSE);
pDlg->OnDlgEnd();
return 0;
}
WM_QUIT は自分で Postしてはいけないメッセージです。(MSDN参照)
終了要求用に別のメッセージをそのスレッドに送りつけて、
スレッド内からPostQuitMessage で自壊すればよさそうに見えます。
Banさん ご意見ありがとうございます。
しかし、私の理解力では意味がわかりません。
もう少し具体的な箇所を教えてください。
何の関数を変更すればよいのでしょうか?
(WIN32系MFCの)マルチスレッドには2種類あり、1つは「ワーカー(作業)スレッド」
で
CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID
pParam, ・・・ );
もう1つは「ユーザー インターフェイスレッド」です。
CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, ・・・ );
村田製さんのコードを拝見すると、テストプログラム的な雰囲気を感じます。
#外していたら、ごめんなさい
で、「ワーカー(作業)スレッド」として生成していますが。
終了は「ユーザー インターフェイス スレッド」で終わろうとしてませんか?
「スレッドの正常終了」の引用です。(MSDN参照)↓
ワーカー スレッドでは、スレッドの正常終了は単純です。制御関数を終了し、終了理由を呼び
出し側に返すだけです。これには関数 AfxEndThread を使うことも、return ステートメント
を使うこともできます。通常、0 を返して正常終了を通知しますが、返す値はプログラマが決め
ることができます。
ユーザー インターフェイス スレッドの正常終了処理も単純です。終了するスレッドから、関
数 ::PostQuitMessage を呼び出すだけです。引数はスレッドの終了コードだけです。ワーカ
ー スレッドの場合と同じように、通常は 0 を返して正常終了を通知します。
・・・さん ありがとうございました。
スレッドに関しては初期の頃より理解が深まりました。
修正箇所ですが、下記の通りです。
しかし、これを実行しますとエラーになります。
何がいけないのでしょうか?
void XXXXDlg::OnDlgEnd(){
<<=====消去
CDialog::OnCancel();
}
static UINT DeleteFunc( LPVOID pParam)
{
XXXXDlg* pDlg = (XXXXDlg*)pParam;
for( int i = 0; i < FullPath.size(); i++ ){
処理
:
}
pDlg->ShowWindow(FALSE);
pDlg->OnDlgEnd();
AfxEndThread(0); <<=====追加
return 0;
}
エラー箇所です。
wincore.cpp 3475行目
int CWnd::RunModalLoop(DWORD dwFlags)
{
ASSERT(::IsWindow(m_hWnd)); // window must be created
ASSERT(!(m_nFlags & WF_MODALLOOP)); // window must not already be in
modal state
// for tracking the idle time state
BOOL bIdle = TRUE;
LONG lIdleCount = 0;
BOOL bShowIdle = (dwFlags & MLF_SHOWONIDLE) && !(GetStyle() &
WS_VISIBLE);
HWND hWndParent = ::GetParent(m_hWnd);
m_nFlags |= (WF_MODALLOOP|WF_CONTINUEMODAL);
MSG* pMsg = &AfxGetThread()->m_msgCur;
// acquire and dispatch messages until the modal state is done
for (;;)
{
ASSERT(ContinueModal());
// phase1: check to see if we can do idle work
while (bIdle &&
!::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))
{
ASSERT(ContinueModal());
// show the dialog when the message queue goes idle
if (bShowIdle)
{
ShowWindow(SW_SHOWNORMAL);
UpdateWindow();
bShowIdle = FALSE;
}
// call OnIdle while in bIdle state
if (!(dwFlags & MLF_NOIDLEMSG) && hWndParent !=
NULL && lIdleCount == 0)
{
// send WM_ENTERIDLE to the parent
::SendMessage(hWndParent, WM_ENTERIDLE,
MSGF_DIALOGBOX, (LPARAM)m_hWnd);
}
if ((dwFlags & MLF_NOKICKIDLE) ||
!SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX,
lIdleCount++))
{
// stop idle processing next time
bIdle = FALSE;
}
}
// phase2: pump messages while available
do
{
ASSERT(ContinueModal()); <<=====エラー箇所
// pump message, but quit on WM_QUIT
if (!AfxGetThread()->PumpMessage())
{
AfxPostQuitMessage(0);
return -1;
}
// show the window when certain special messages
rec'd
if (bShowIdle &&
(pMsg->message == 0x118 || pMsg->message
== WM_SYSKEYDOWN))
{
ShowWindow(SW_SHOWNORMAL);
UpdateWindow();
bShowIdle = FALSE;
}
if (!ContinueModal())
goto ExitModal;
// reset no idle state after pumping normal
message
if (AfxGetThread()->IsIdleMessage(pMsg))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE));
}
ExitModal:
m_nFlags &= ~(WF_MODALLOOP|WF_CONTINUEMODAL);
return m_nModalResult;
}
> CDialog::OnCancel();
をダイアログが所属しているのとは別のスレッドが実行したらだめ。
ダイアログの所属スレッドに EndDialog(IDCANCEL) を実行してもらうように。
> AfxEndThread(0); <<=====追加
は要らない。制御関数を return で抜ければ、スレッドは自然に消滅するから。
つまり、こういう事ですか?
これを実行しても先ほど書いたwincore.cpp 3475行目で落ちます。
なにがいけないのでしょうか?
void XXXXDlg::OnDlgEnd(){
EndDialog(IDCANCEL);
}
static UINT DeleteFunc( LPVOID pParam)
{
XXXXDlg* pDlg = (XXXXDlg*)pParam;
for( int i = 0; i < FullPath.size(); i++ ){
処理
:
}
pDlg->ShowWindow(FALSE);
pDlg->OnDlgEnd();
return 0;
}
OnDlgEnd() を実行しているのがワーカースレッドだから、
OnDlgEnd() の中に書かれている EndDialog() もワーカースレッドが
実行してしまいます。
EndDialog() はダイアログを作成した側のスレッドが、
ワーカースレッドの終了を検知した時点で実行するようにすればいいです。
完全ではありませんが、なんとなくわかりました。
DeleteFuncの中の処理でOnDlgEndを呼んではいけないという事ですよね?
しかし、OnInitDialogで分岐しているので、
日々更新される関数でも無い限り、スレッドが終了したかどうかの確認は
取れないと思います。
うーん。難しいです。
CDialogの中にON_UPDATE_COMMAND_UIみたいな関数があればそこに
終了したかどうかのフラグをチェックする処理を起きたいのですが。
そんなメソッドありませんよね。
#外してたらごめん
ワーカースレッドからダイアログに、WM_CLOSE でも投げればいいんでわ?
DeleteFuncスレッドからメインスレッドに
「終わったよー」と通知すればよいのでは?
通知はPostMessage等でできます。
> 日々更新される関数でも無い限り、スレッドが終了したかどうかの確認は
> 取れないと思います。
こっちの方法でももちろん可能です。
たとえば、WM_TIMER。
MFC独自のWM_KICKIDLEというのもあります。
ワーカースレッドが return の直前にダイアログ宛に「終了するよ」という
ユーザー定義メッセージをポストしてやればいいです。
ダイアログ宛のメッセージはダイアログを作成したスレッドに届けられます。
メッセージハンドラはダイアログを作成したスレッドで実行されるので、
そこで EndDialog() を呼び出してやる。
ただし、メッセージを受け取った時点でワーカースレッドが本当に return まで
実行し、消滅している保証は無いです。
その後、本当に終了するまで待ち合わせるには WaitForSingleObject() などを使う
必要があります。まぁ、待つといってもほんの数ミリ秒でしょうけど。
AfxGetMainWnd()->PostMessage(WM_CLOSE);
を追加したのですが、メインのプログラムごと終了してしまいました。
ダイアログのみを終了させるには、どうすればよいのでしょうか?
static UINT DeleteFunc( LPVOID pParam)
{
XXXXDlg* pDlg = (XXXXDlg*)pParam;
for( int i = 0; i < FullPath.size(); i++ ){
処理
:
}
pDlg->ShowWindow(FALSE);
//pDlg->OnDlgEnd();
AfxGetMainWnd()->PostMessage(WM_CLOSE); <<========== 追加
return 0;
}