- ベストアンサー
待機関数(WaitForMultipleObjects等)について教えてください
Windows2000又はXPで動作させるプログラムを作成していますが、スレッド(AfxBeginThread()で作成した処理)からは待機関数(WaitForMultipleObjects等)は正常に動作しますが、画面のボタン等のクリックイベントからは正常に動作しませんが、これは何故でしょうか?HELPで「このハンドルは、SYNCHRONIZE アクセスを持っていなければなりません」と出ていましたが、何か方法があるのでしょうか? ちなみに言語はVC++6.0です。
- みんなの回答 (4)
- 専門家の回答
質問者が選んだベストアンサー
>実は下記のようなスレッドを使用しないで使いたかったのですが ボタン2のイベントハンドラに入ると、ボタン2のイベントハンドラを抜けるか、コンテキストスイッチが発生しない限り、他のイベントハンドラに制御が渡される事はありません。 そして、WaitForSingleObjectがINFINITEで待つ間、該当のプロセスは停止しているので、ボタン1をクリックしてもボタン1のイベントハンドラが実行される事はありません。 つまり、ボタン2のイベントハンドラ内でボタン1を押されるのを待っても、ボタン1のイベントハンドラは実行されない為、オブジェクトがシグナル状態になる事はありません。
その他の回答 (3)
- chie65536(@chie65535)
- ベストアンサー率44% (8755/19867)
>今まで通り待ちのループでPeekMessageでメッセージをディスパッチする方法で行きます。 >while(PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE) != 0) >{ >TranslateMessage(&Msg); >DispatchMessage(&Msg); >} 上記の方法は、Windowsプログラミングでは推奨されません。と言うか、やってはいけません。 本来、メッセージループはメイン関数の1ヶ所のみで行うべきです。 しかも、自分でメッセージループを書かないフォームアプリケーションの場合、ライブラリで用意されたメッセージループは while (GetMessage(&msg,NULL,0,0)) { if (hFindWnd && IsDialogMessage(hFindWnd,&msg)) continue; if (hHLPWnd) { if (PropSheet_GetCurrentPageHwnd(hHLPWnd) == NULL) { DestroyWindow(hHLPWnd); hHLPWnd = NULL; } else if (PropSheet_IsDialogMessage(hHLPWnd,&msg))continue; } if (!TranslateAccelerator(hMainWnd,hAccel,&msg) && !TranslateMDISysAccel(hClientWnd,&msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } のように、非常に複雑です(実物はもっともっと複雑です) 質問者さんのように「取りあえずのメッセージのディスパッチ」では、ダイアログ、MDI、HELPに対応できません。 もし、そのアプリの中でダイアログ、MDI、HELPを用いた瞬間、プログラムは「応答なし」になります。 >実はあるユニットと通信をしているので、完了待ちの処理で はっきり申し上げると「完了待ちを行う事自体が間違いであり、Windowsアプリケーションでは『待ちループ』を作ってはいけない」のです。 質問者さんが書くべき処理は、以下のようになります。 ・メッセージループではなく、WindowProcコールバック関数に、通信終了メッセージを受け付けた場合の処理を追加する。そして、そこで「『通信中』ダイアログを、IDOKで閉じる」と言う処理を行う。もちろん、『通信中』ダイアログが開かれていない場合は、このメッセージは「処理済み」として扱う。 ・ボタン押下イベントで、スレッドに通信開始を指示する。 ・スレッドに通信開始を指示した後に、「通信中」の表示を行うダイアログをモーダルで表示する(メインプログラムはここで停止し、先に進まない。モーダルなダイアログがあるのでフォーカスを受け取る事もできなくなる) ・「通信中」ダイアログには「通信中断」のボタンだけがあり、これを押すとメインプログラムのボタン押下イベント処理の所に制御が戻る(呼び出し元にはIDCANCELが返る) ダイアログ表示中、実際のプログラムの制御は、既存の「正規のメッセージループ」にあり、その「正規のメッセージループ」の中からWindowProcコールバック関数が呼ばれ、そこで通信終了のメッセージを判定する事となる。 ・ボタン押下イベントで「通信中」の表示を行うダイアログを表示したあと、IDCANCELが返って来たら、スレッドに通信中断を指示するなど、後始末を行う。IDOKが返って来た場合は通信が終了しているので、後始末を行う。 ・表示した「通信中」ダイアログを解放(Release)して、ボタン押下イベント関数から抜ける。 このようにすると、既存の「正規のメッセージループ」でメッセージを待つ事となり、ダイアログ、MDI、HELPを追加しても「正規のメッセージループが、すべてちゃんと処理してくれる」ので、プログラムが「応答なし」になる事はありません。 繰り返しますが ************************************** *****プログラムの正規の場所以外で、メッセージループをしてはいけない***** ************************************** です。
お礼
回答ありがとうございます 何故か回答を戴いたメールが遅れて来て、お礼が遅くなり申し訳ありません。 ご意見を参考に一度プログラム構成を見直して見ます。
- machongola
- ベストアンサー率60% (434/720)
こんばんは。 タイマー使うか、 http://msdn.microsoft.com/ja-jp/library/49313fdf(VS.80).aspx http://msdn.microsoft.com/ja-jp/library/sfs37s7c(VS.80).aspx http://msdn.microsoft.com/ja-jp/library/fy1hkbdk(VS.80).aspx スレッドにメッセージを送るか、 http://msdn.microsoft.com/ja-jp/library/cc410979.aspx で出来るのではないでしょうか。以下はスレッドにメッセージを送る方です。此れでは駄目でしょうか。 //イベントメッセージ const UINT WM_MYEVENT = WM_USER + 1000; //スレッド関数 static UINT Proc(LPVOID pData) { MSG msg; CDlg* p = static_cast<CDlg*>(pData); //エディットコントロールがあるとする p->SetDlgItemText(IDC_EDIT1, "START"); while(::GetMessage(&msg, NULL, 0, 0)) { switch(msg.message) { case WM_MYEVENT: //スタティックコントロールに表示 p->SetDlgItemText(IDC_STATIC, "Event occurred"); break; default:; } } //エディットコントロールがあるとする p->SetDlgItemText(IDC_EDIT1, "END"); return 0; } //待機解除 void CKaiketuDlg::OnButton2() { // TODO: この位置にコントロール通知ハンドラ用のコードを追加してください if(m_pThread) { //イベントメッセージを送る m_pThread->PostThreadMessage(WM_MYEVENT, 0, 0); //スレッドを閉じる m_pThread->PostThreadMessage(WM_QUIT, 0, 0); m_pThread = NULL; } } //待機開始 void CKaiketuDlg::OnButton1() { // TODO: この位置にコントロール通知ハンドラ用のコードを追加してください if(m_pThread == NULL) m_pThread = ::AfxBeginThread(&::Proc, this); }
お礼
machongolaさん回答ありがとうございます。 本来の目的を説明せずに内容だけ質問した為ご心配をして頂きありがとうございました。目的はボタンクリック等の処理から、ある装置と通信を行います。この時装置からの完了(実際の送信と応答の受信はスレッドで処理しています)を待って次のコマンドを送る必要がある為、完了待ちを行う必要があり、現行PeekMessageを使用してメッセージのディスパッチを行っていたのですが、もっとCPUを軽減出来る策が無いかと思い、イベント待ちが行えないか質問をさせて戴いた次第です。次回の新規プログラム作成時の参考にさせて頂きます。
- wathavy
- ベストアンサー率22% (505/2263)
こんばんわ。(まだ早いかも) WaitForSingleObjectではありますが、テストを作成してみました。 ボタンを押して、CEvent の変数をSetEvent()すると期待通り動作します。スレッドからでなくて、どこからでも動作するようですが。 WaitForMultipleObjectではテストしていません。 あまり使わないので・・・。 BOOL CthrdtstDlg::OnInitDialog() // TODO: 初期化をここに追加します。 bl_draw = true; AfxBeginThread(WriteLetters,this); //AfxBeginThread(Terminate,this); pEvent = new CEvent(false, false); UINT WriteLetters (LPVOID pParam) { static CthrdtstDlg* pDlg; pDlg = (CthrdtstDlg*)pParam; //pDlg->ApplyThreashToMono(); while(pDlg->bl_draw){ pDlg->WriteText(); } if(!WaitForSingleObject(pDlg->pEvent->m_hObject,INFINITE)){ pDlg->GetDlgItem(IDC_STATIC)->SetWindowTextW(L"Event occurred"); } return true; } void CthrdtstDlg::WriteText(void){ GetDlgItem(IDC_STATIC)->SetWindowTextW(L"Running"); Sleep(1000); GetDlgItem(IDC_STATIC)->SetWindowTextW(L"Still Running"); Sleep(1000); } void CthrdtstDlg::OnBnClickedButton2() { // TODO: ここにコントロール通知ハンドラ コードを追加します。 this->pEvent->SetEvent(); }
お礼
wathavyさん早速の回答をありがとうございました。 私の説明が悪かったようです。実は下記のようなスレッドを使用しないで使いたかったのですが、私自身は組み込み系のプログラムが長く、Windowsプログラムはあまり詳しくないので、もしかしたら常識はずれの内容だったのかもしれません。 実はButton2で待機待ちを行い、Button1でButton2の処理の待ち解除が行えないのかなと思って質問をしましたが、この方法は無理なような気がして来ました。どうもありまとうございました。 void CTestDlg::OnButton1() { // TODO: この位置にコントロール通知ハンドラ用のコードを追加してください this->m_pEvent->SetEvent(); } void CTestDlg::OnButton2() { // TODO: この位置にコントロール通知ハンドラ用のコードを追加してください if(!WaitForSingleObject(m_pEvent->m_hObject,INFINITE)) { GetDlgItem(IDC_STATIC)->SetWindowTextW(L"Event occurred"); } AfxMessageBox("END"); }
お礼
chie65535さん的確な回答ありがとうございました。 なんとなく薄々感じてはいたのですが、ボタンクリック等の処理からでは出来ないのですね。実はあるユニットと通信をしているので、完了待ちの処理でWaitForSingleObject等が使えればCPUも軽減出来ると思ったのですが、今まで通り待ちのループでPeekMessageでメッセージをディスパッチする方法で行きます。 while(PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE) != 0) { TranslateMessage(&Msg); DispatchMessage(&Msg); }