別スレッド内でShowWindowによる表示・非表示させるには?

[戻る]
ebimayo 2006/03/28(火) 11:17:15
いつもここにお世話になっています。

タイトルの件で試したり調べて分からなかったので
教えてもらえないでしょうか。

<<やろうとしている事>>
1.CStatic(他にもCBitmapButtonなどもありますが)の派生クラスを作成し、
  文字やビットマップの表示・非表示(点滅させたい)
(上記の処理は動いているのですが以下の条件下で処理が固まって戻ってこない
 ことがあります。)


<<固まる条件>>
1.点滅させるための処理を動かして、「OK」ボタンクリックで終了させると
  固まります


<<実現のために試した方法と結果>>
1.基本のスケルトンはダイアログベース(MFC)で作成
2.ダイアログ側では、点滅させるクラスのnewで起こし、OnDestroy()でDelete
3.点滅をクラスの外から実行させるためにBlinkStartをメンバ関数として作成
4.BlinkStart内で別スレッド起動(AfxBeginThread)
5.クラス破棄時に3で起動したスレッドの終了を同期させる(WaitForSingleObject)

// 点滅処理を開始するためのメンバ関数
void CXXXX::BlinkStart()
{
    m_hEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
    m_pThread = AfxBeginThread( BlinkThread, (LPVOID)this,
                                THREAD_PRIORITY_NORMAL, 0, 
                                CREATE_SUSPENDED, NULL );
    if( m_pThread != NULL ){
        m_pThread->m_bAutoDelete = FALSE;
        m_pThread->ResumeThread();
    }
}

// 点滅処理を実行するスレッド
UINT BlinkThread( LPVOID pParam )
{
    CXXXX      *pThis;
    DWORD  dwRtn;
    BOOL   bShow;
    
    pThis = (CBaseFrame*)pParam;
    bShow = pThis->IsWindowVisible();
    while( true ){
        dwRtn = WaitForSingleObject( pThis->m_hEvent, (DWORD)50 );
        if( dwRtn == WAIT_OBJECT_0 ){ break; }
        if( bShow ){
            pThis->ShowWindow(SW_HIDE);
            bShow = FALSE;
        }else{
            pThis->ShowWindow(SW_SHOWNOACTIVATE);
            bShow = TRUE;
        }
    }
    return 1;
}

// クラスのデストラクタ
CXXXX::~CXXXX()
{
    DWORD     dwCode;
    BOOL  bRtn;

    if( m_pThread ){
        bRtn = ::GetExitCodeThread( m_pThread->m_hThread, &dwCode );
        if ( bRtn && dwCode == STILL_ACTIVE ) {
            SetEvent(m_hEvent);
            WaitForSingleObject( m_pThread->m_hThread, INFINITE );
        }
    }
    if( m_pThread != NULL ){    delete m_pThread;    }
    if( m_hEvent != NULL ){    CloseHandle(m_hEvent);    }
}

上記のような処理を書いて実行し、「OK」ボタンクリックでダイアログを閉じると、
OnDestroy()でクラスをdeleteするときにCXXXX::~CXXXX()が呼ばれ、その中の
WaitForSingleObjectで処理が戻らなくなりました。

色々試してみて、以下のいずれかの処置で固まることが無くなりました
1.スレッド内でShowWindowを実行させないようにする
2.「OK」ボタンのOnClickedイベントをオーバーライドしてOnOKを呼ぶ前に
  CXXXXクラスをdeleteする

<<知りたいこと>>
1.PostMessageなどShowWindowではない方法で表示・非表示が可能か?
(ちなみに、BlinkThread内に
 ::PostMessage(pThis->GetSafeHwnd(),WM_SHOWWINDOW,FALSE,0);
 と、書いても表示・非表示自体できませんでした)
2.OnOKによる終了時に表示・非表示処理(ShowWindowなど)を行うと
  このように固まるものなのか?
3.回避策


以上、宜しくお願いします。

開発環境
Microsoft WindowsXP(SP2)
Microsoft Visual C++ 2005
  ダイアログベースアプリケーション(MFC)

dairygoods 2006/03/28(火) 11:35:08
>1.PostMessageなどShowWindowではない方法で表示・非表示が可能か?

ShowWindowAsync  という関数があります。

> 2.OnOKによる終了時に表示・非表示処理(ShowWindowなど)を行うと
>  このように固まるものなのか?

「OnOKで」というのが直接の原因ではないでしょう。
両方のスレッドが(ShowWindowとWaitForSingleObject)でお互いの
応答を待っているのでデッドロックになっています。

> 3.回避策

こういうことはタイマー(SetTimer)を使ったほうが
ややこしくならないと思います。

ebimayo 2006/03/28(火) 12:10:40
dairygoodsさん ありがとうございます。

ShowWindowAsyncを使うことによって固まることなく動くようになりました。

ShowWindowとWaitForSingleObjectでデッドロックになっているとは
気がつきませんでした。
OnOKなどによる終了時以外(他の適当なボタンのクリックイベントなど)で
CXXXXクラスの破棄をしてもデッドロックしないので、OnOKなどによる終了時は
ShowWindowは有効でなくなるウィンドウ(?)に対して処理を行うことで
WaitForSingleObjectとのデッドロックが起こるのでしょうか?
この辺が未だにわかってない・・・


SetTimerについては、他の処理の兼ね合いもあって考えに入ってなかったですが、
SetTimerを使った方法についても考えてみようと思います。

実現したいことが出来たので「解決」にチェック入れますが、
出来ればShowWindowとWaitForSingleObjectでなぜデッドロックするのか
もう少しだけでも教えてもらえたらお願いします。

ebimayo 2006/03/28(火) 12:25:04
[[解決]]
チェックいれてなかった

dairygoods 2006/03/28(火) 12:26:45
スレッドA がスレッドB に対して、WaitForSingleObjectし、
スレッドB がスレッドA に対して、WaitForSingleObjectしたら、
デッドロックになることは分かりますか?

で、ShowWindowも内部でWaitForSingleObjectと同じことをしているのです。

ebimayo 2006/03/28(火) 12:49:29
再度、回答ありがとうございます。

> スレッドA がスレッドB に対して、WaitForSingleObjectし、
> スレッドB がスレッドA に対して、WaitForSingleObjectしたら、
> デッドロックになることは分かりますか?

これは分かります。


> で、ShowWindowも内部でWaitForSingleObjectと同じことをしているのです。

ShowWindowは内部で::ShowWindowを呼んでいて、それ以降はステップ実行
で終えなかったのでわかりませんでした。
見方が間違ってるのかな

PATIO 2006/03/28(火) 13:35:50
思うにMicrosoft的にはCWndクラスのポインタをスレッド間で
やり取りしないようにしなさいと書いているので止めた方がよいと思います。
今の実装では動くみたいですが、
製造元がやめなさいと言っているのでもし動かないような仕様変更がなされても
文句は言えないです。(しないように書いていたはずと言われるでしょう)
ユーザー定義のメッセージをCXXXXに対して送ってCXXXX側でShowWindowを呼んでも
良いような気もしました。
あと、メッセージの送り先をスレッドに教える為にCXXXXのウインドウハンドルを
引き渡しておけばよいと思います。

ebimayo 2006/03/28(火) 14:59:55
PATIOさん 回答ありがとうございます。

おっしゃる通り、Microsoft的に推奨しない方法のようですね。

ただ、CXXXXのウインドウハンドルを渡してスレッド内部でShowWindowすることも
試していたのですが、その場合もデッドロック?で固まってしまうケースが
あり悩んでいました。

たぶんMicrosoft的に推奨(?)な方法としてウィンドウハンドルをスレッドに渡して
そのウィンドウハンドルにPostMessageして元のスレッド内で処理(今はShowWindow)
させると上手く動きました。

その結果から、スレッドに渡すものが原因ではなく、(と思ったので)
OnOKなどのダイアログ終了時になんらかの要因がるのか?
ShowWindowで終了するときのウィンドウにアクセスするために起きている
ようなのでShowWindowに代わるものがあるのか?
ということが知りたく、知りたいことの1,2,3の質問を書き込みました。

そこでdairygoodsさんの回答していただいた、
「ShowWindowも内部でWaitForSingleObjectと同じことをしているのです」
で、納得できました。(ShowWindowの中身見れてないので感覚的にですが)

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