- ベストアンサー
別スレッドでデータを受信できないときの解決方法
- Windowsのメインスレッドで別スレッドのデータを受信できる方法について調査しました。
- メッセージキューの格納順序がわからないため、正しいデータを切り出すことができません。
- メッセージ周りに関する基本理解が不足しており、適切な解決方法を見つけることができませんでした。
- みんなの回答 (6)
- 専門家の回答
質問者が選んだベストアンサー
こんにちは。 >このループ内ではOSに処理が戻りません。これがUpdateしない理由ではないかと考えます。これも前に書きましたが、pViewが属するメッセージポンプ内でDispatchMessageが発行されてpViewの >while(!end){ >if (sel) >data_a作成送出 >else >data_b作成送出 >} が終了するまではDispatchMessageから制御が返りませんから、次のメッセージキューからメッセージを取り出せないから、WM_USER_RCVSTSが留まり続けてCIlcView::OnRvcStsは呼ばれないということかと。 つまり、CIlcView::OnRvcStsと上記無限ループが同一スレッド上である以上、同時に実行されないということです。 >messageの仕組みはよくわかりません。なにか方法はありませんでしょうか?。 原因は1つのスレッドにメッセージポンプと上記のループの2つが存在しているからで、これも前回書いたように、 while(!end){~~~}を別スレッドに移して、そのスレッドのメッセージポンプにこの機能を加えないといけないのではないでしょうか。 MFCのメッセージポンプの機能 + while(!end){~~}の機能をそのスレッドのループ( CWinThread::Run() )にオーバーライドして実装するということです。あとは、このスレッドにPostThreadMessageで受信データを渡せばいいでしょう。 ということでメッセージの仕組みがわからないでは問題は解決できないと考えます。まずは、winmain.cpp、thrdcore.cpp(特に後者)の実装 をよくみてみることから始めてください。
その他の回答 (5)
- Dodonpa2
- ベストアンサー率82% (19/23)
こんにちは。 dataは受信スレッドもpViewもOnRcvStsも同じものを 指しているのですか? OnRcvStsのwParam値がpViewの無限ループのdataに渡せない ことは未だに理解しがたいのですが、それは是として、 pView側で!endがいつtrueになるかということも関係する かもしれませんが、2.のループはやっぱりよろしくない ように思います。 そこで別提案ですが、送信用スレッドを作ってそこで 2.のループを実装していかがでしょう?つまり、pViewの あるメインスレッド、1.の受信スレッド、2.の送信用 スレッドの3つです。 ユーザーインターフェイススレッドを作るのでしたら、 CWinThread::Run()をオーバーライドして、2の機能を 含めて実装します。受信スレッドから、この作成した 送信用スレッドに対してPostThreadMessageをポストします。 ワーカースレッドでも結局同じことですが、メッセージポンプ の機能+2.の機能をスレッド制御関数に実装して 受信スレッドからPostThreadMessageを送信する形になります。 こうすれば、pViewはフリーズしないし、現在躓いている箇所も 解決しませんかね? ただ、Run()をオーバーライドする時点で相当難度は高くなる ことは覚悟されたほうがいいと思います。
補足
Dodonpa2様 再三の回答ありがとうございます。 やはり LRESULT CIlcMyView::OnRcvSts(WPARAM wParam, LPARAM lParam) { sel = wParam; return(0); } このselというデータをpViewクラスで取り出せません。正確にはpViewクラスでのループが終了した時点でselは取り出せます。つまりUpdateします。 pViewクラスのプログラム while(!end){ if (sel) data_a作成送出 else data_b作成送出 } このループ内ではOSに処理が戻りません。これがUpdateしない理由ではないかと考えます。 ようするにメッセージキューの出力を利用できません。 messageの仕組みはよくわかりません。なにか方法はありませんでしょうか?。 このループは別の理由で専用スレッドに独立させるつもりはありました。ループ中に別の仕事をさせたいと考えたからです。 selをサブスレッドからサブスレッドに送る方法がわからずまだ試みていません。 PostThreadMessage()というものがあるようですが。
- Dodonpa2
- ベストアンサー率82% (19/23)
ハードウェア ↓ 受信スレッド・・・無限ループで待機 ↓(PostMessage発行) FormView ↓(供給データを作成) ハードウェア こういうことでいいのでしょうか?未だに全体像がつかめないので 的外れかもしれませんが、受信スレッドで無限ループで待機ってのが いやですね。 ハードウェアからどのようにデータを受け入れているかにもより ますが、CEventの同期オブジェクトとか使って待機してはどうでしょう? 3に関しては、CIlcMyViewにメンバ変数用意しておいて CIlcMyView::OnRcvStsでその変数にコピーすればいいのでは? そういう単純な問題でない? 4.自作DoEvents作るのでしたら、普通新たなスレッド作りますし、 1つのメッセージキューで2つのポンプが存在していたらどういう 動作になるか不明かと思います。(やったことないので) CWinThread::Run()内のDispatchMessageでメッセージを送出後、 FormViewのウィンドウプロシージャでこのDoEventsが実行されたら、 ウィンドウプロシージャは終了せず、その戻値を待っている DispatchMessageが固まりますよね。 もちろん、DoEventsでメッセージは処理していますが、MFCの メッセージポンプはその他にもアイドル処理(CWinThread::OnIdle)が 実行されていたりするので、おかしなことになる可能性は 当然出てくるかと。 FormView側も無限ループではなく、CEventを使ってCEvent::SetEvent() でトリガーしてデータ送り出すというようなことはできないので しょうか?
補足
Dodonpa2様 何度もお付き合いくださりありがとうございます。 状況の説明が明確ではありませんでした。以下に箇条書きします: 1.受信スレッド while(monitorDev_FlagExitThread == TRUE){ recvSize = recv(s, buf, sizeof(buf), 0); data = strtol(str, NULL, 16); ::PostMessage(pView->hWnd, WM_USER_RCVSTS, (WPARAM)data, (LPARAM)data); return 0; } 上記のようにsocket接続されたハードウエアからデータをループして取り込んでいます。 recvがBlocking動作をしているのでこのループはハードウエアの更新速度に一致し50msecです。 これは与件としなければなりません。 データ内容は間違いありません。 2.pView側 こちらもデータ作成送出のためにループが組まれています。このループは受信スレッドのそれとはまったく同期していません。 hWnd = GetSafeHwnd(); while (!end){ if (data) data_a作成送出 else data_b作成送出 } このdataが受信スレッドから送られてくると期待しているものです。 3.Viewクラスでの受け手 LRESULT CIlcMyView::OnRcvSts(WPARAM wParam, LPARAM lParam) { data = wParam; return(0); } このdataはこの関数の中では確かに変化していますが、外側つまり2項のような形では取り出せません。 つまり関数内は受信スレッドに属しているようです。現に2項の主要部分を関数内に記述したところ動作しました。 ただしwhile文の記述は好ましくないようです。 これが解決すべき課題です。 4.非同期性 仮にdataが取り出せ2項記述が可能になったとした場合、二つのループは同期していません。dataを参照しただけでメッセージポンプしてしまうとしたらアンバランスが発生するでしょう。現に2項を関数内に記述したとき、それが原因と伺わせる障害がありました。 どうも基本的なことが分かっていません。もう少しお教え願えれば幸いです。
- Dodonpa2
- ベストアンサー率82% (19/23)
こんにちは。 >送受信ともWhile(1)ループの中を回っています。 アプリ全体の話になってきたので確認ですが、 メッセージ送信スレッド(PostMessage発行スレッド)は、 インターネットなどからリアルタイムでデータ(data)を拾って、データ取得段階でメッセージ受信スレッド(FormView)にメッセージを飛ばして いると言う理解でいいでしょうか? ということでしたら、PostMessage発行スレッドの無限ループはわかりますが、FormView側の無限ループ、つまり >実際にデータを使用する受信側Whileループの中に >OnRcvSts(wParam, lParam); >を記述しました。 はちょっとまずいかもしれません。PostMessageでFormViewのスレッドのメッセージキューにWM_USER_RCVSTSがどんどん溜め込まれ、メッセージポンプで該当のウィンドウ、この場合はFormViewにディスパッチされ、FromViewのウィンドウプロシージャによりCIlcView::OnRCVSTSが呼ばれます。 つまり、プログラムで無限ループしなくてもMFCのフレームワークがWM_USER_RCVSTSがポストされた回数だけOnRcvStsが必ず呼ばれます。前回も書きましたが、他のスレッドからやって来るメッセージを無限ループであるCWinThread::Run()で監視しているので、わざわざプログラマが 重複するような機能を実装する意味はないということです。
補足
Dodonpa2様 報告が遅れ申し訳ありません。 1.システム全体 WinodwsマシンにハードウエアがEthenetで接続されていて、ハードウエアからは周期的に動作データが送られてきます。Windows myProgramはこれを受信スレッドというもので受け、このスレッドもループで構成されています。そのうちの一部はFormViewクラスでコントロールに使用されます。 コントロールプログラムの役目はハードウエアに間断なくデータを供給することです。ハードウエアの実行状況をモニタし遅滞なくデータを送り込む役目です。その供給データを用意するためにループが組まれています。 このON/OFF信号は受信スレッドから数msec遅れで取得したいところです。 2.FormViewクラスでの受信スレッドデータの取得 これはできることが分かりました。ただしその後分かったのはデータの更新が行われないことです。受信スレッドからは間断なくデータが送られてきているのでその都度更新は行われるのだと思っていました。 そこでやむなく前回も申し上げた自作のDoEvents()というものをデータ作成ループに入れたところ受信スレッドの内容が反映されるようになりました。 3.データ作成プログラムの記述場所 OnRcvSts(wParam, lParam){ ・・・・ } の中に記述しないとwParam,lParamは利用できないようです。やむなくWhileループで構成されるデータ作成プログラム全体をこの中に移しました。 4.不安定 なぜか不安定です。受信スレッドから受け取るハードウエアSTatusが不安定なのかもしれません。 本来ならOnRcvSts{}の外側にdata=OnRcvSts(wParam, lParam); int OnRcvSts(WPARAM wParam, LPARAM lParam) { return(wParam); } とできれば呼ぶ都度メッセージポンプも行われ好都合なのですが、引数のゴミがそのまま出てきてしまいます。 不安定の原因はDoEvents() DWORD CIlcMyView::DoEvents(VOID) { MSG msg; while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ){ if ( msg.message == WM_QUIT ){ return( msg.message ); } TranslateMessage( &msg ); DispatchMessage( &msg ); } return( 0 ); } にあるのかもしれません。 もう一度お教え願えると助かります。
- Dodonpa2
- ベストアンサー率82% (19/23)
こんにちは。 gammodlerさんのそれぞれのクラスとスレッドの状況が正確に把握できてないです。 <メインスレッド(メッセージ受信側)> ・CIlcMMIView(CFormViewの派生クラス) であることは分かりますが、3.の CIMyViewはメッセージ送信側のビューという理解でよろしいでしょうか? -------------------------------------------------------------------------- 前提条件がはっきりしないので、正確なことを申し上げられませんが、とりあえず 2.のメッセージ受信側で >ON_MESSAGE(WM_USER_RCVSTS, OnRcvSts) と記述しているので、WM_UESR_RCVSTSのメッセージハンドラは 6.のCIlxxxView::rcvStrではなく、CIlxxxView::OnRcvStsですから、こちらで 実装する必要があることは確かです。 特に3.4.あたりがよく分からないので原則的なことだけ書きます。 1.メッセージの送信・受信の両スレッドの各ウィンドウでインクルードされるヘッダファイル (stdafx.hなど)に #define WM_USER_RCVSTS (WM_USER + 0x1000) を宣言。 2.メッセージ受信側スレッドのウィンドウ(CIlcView)のヘッダファイルに afx_msg LRESULT OnRcvSts(WPARAM wParam, LPARAM lParam); を宣言。 3.2.のメッセージ受信側ウィンドウの実装ファイルのメッセージマップに BEGIN_MESSAGE_MAP(CIlcView, CFormView) ON_MESSAGE(WM_USER_RCVSTS, &CIlcView::OnRcvSts) END_MESSAGE_MAP() と記述した上で、 LRESULT CIlcView::OnRcvSts(WPARAM wParam, LPARAM lParam) { //・ //・ return 0; } とハンドラを実装します。 ここまでがメッセージ受信側スレッドのウィンドウの実装です。 メッセージ送信側は、 PostMessage(hWnd, WM_USER_RCVS, (WPARAM)GetCurrentThreadId(), 0); を呼べば、WPARAMに送信側のスレッドIDを含めて、メインスレッドにポストされ、 メッセージポンプはCIlcViewにディスパッチして、CIlcView::OnRcvStsが実行されます。 ※なお、メッセージポンプの実体はCWinAppの基底クラスCWinThread::Run()でMFCのthrdcore.cpp にあるので確認されることをお勧めします。
補足
Dodonpa2様 迅速かつ詳細回答ありがとうございます。 ご指摘の件、すべて修正しました。寝ぼけていました。済みません。 送信側: ::PostMessage(hWnd, WM_USER_RCVSTS, (WPARAM)GetCurrentThreadId(), data); 受信側(FormView): LRESULT CIlcMYView::OnRcvSts(WPARAM wParam, LPARAM lParam) { wParam = wParam; lParam = lParam; data = lParam; return(0); } で送信側でdata=0x1234; とすると受信側で同じデータが取得できることが分かりました。 送受信ともWhile(1)ループの中を回っています。 ただしとてつもない時間を経てブレークアウトしてきます。 実際にデータを使用する受信側Whileループの中に OnRcvSts(wParam, lParam); を記述しました。 しかしこれでは入力したlParamがそのまま出てきてしまうのですから、全く意味がありません。このようなCallはすべきではないのでしょう。 while(1)はOSにコントロールを戻していません。ですから送信スレッドによりUpdateしたdata値を取り込むことができません。 質問の内容はシフトしてきているのですが、このような場合どうすればいいのでしょう。OSに一時的ににコントロールを戻す方法にSleep(1);があるようですが効果はありません。 私はこのようなケースで自作のDoEvents();なる関数を使っています。: DWORD CIlcMyView::DoEvents(VOID) { MSG msg; while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ){ if ( msg.message == WM_QUIT ){ return( msg.message ); } TranslateMessage( &msg ); DispatchMessage( &msg ); } return( 0 ); } これをループ内に記述するとたしかに送信スレッドでUpDateしたタイミングでdataが書き変わっているらしいのですが、全体としては動作しません。 それから全体がお話にならないほど遅くなります。どこかに迷い込んでいるらしいです。 ご示唆いただけることがありましたらお願いいたします。
- Dodonpa2
- ベストアンサー率82% (19/23)
受信側でもメッセージポンプが働いているので、GetMessageを使う意味が感じられません。MFCならメッセージマップに記述してハンドラ作るとか、CWnd::WindowProcをオーバーライドすればいいかと思います。 スレッドIDを取得したければ、PostMessageのWPARAMやLPARAMでそのIDを渡してはだめなのですか?
補足
Dodonpa2様 回答ありがとうございます。 Windowsメッセージの関連はよくわかりません。getMessage()もよく意味を理解しないで使っていました。 そこでご示唆に従いMFCのマナーに従いコーディングしてみました。: 1.メインスレッドの該当箇所に #define WM_USER_RCVSTS (WM_USER + 0x1000) を追加。 2.CFormviewのcppファイルに追加: BEGIN_MESSAGE_MAP(CIlcMMIView, CFormView) //{{AFX_MSG_MAP(CIlcMMIView) ON_EN_CHANGE(IDC_EDIT_GTE, OnChangeEditGte) ON_EN_CHANGE(IDC_EDIT_REP, OnChangeEditRep) ON_WM_CTLCOLOR() ON_WM_TIMER() //}}AFX_MSG_MAP ON_MESSAGE(WM_USER_RCVSTS, OnRcvSts) END_MESSAGE_MAP() 3.Headerファイルに protected: //{{AFX_MSG(CIMyView) //}}AFX_MSG afx_msg LRESULT OnRcvSts(WPARAM wParam = 0, LPARAM lParam = 0); DECLARE_MESSAGE_MAP() 4.Viewヘッダーファイルに //{{AFX_MSG(CIlcMMIView) afx_msg void ・・・・・・ afx_msg void ・・・・・・ //}}AFX_MSG afx_msg LRESULT rcvSts(WPARAM wParam = 0, LPARAM lParam = 0); DECLARE_MESSAGE_MAP() を追加 5.受信スレッドに while(1){ ・・・・・ ::PostMessage(hWnd, WM_USER_RCVSTS, 0x5678, 0x1234); } を追加 6.ハンドラを追加: LRESULT CIl***View::rcvSts(WPARAM wParam, LPARAM lParam) { printf("wParam %x\n", wParam); printf("lParam %x\n", lParam); return(0); } としました。 しかしrcvStsのところでBreakOutを待ったのですが抜けてきません。 受信スレッド側でWhileループ中に置かれているのでrcvStsには毎回抜けてくるのを期待したのですが、実際はそうはなりません。 メインスレッド側でWhileループを組みその中でrcvSts()を呼んでみたのですが、rcvSts(UINT wParam=0, LONG lParam=0)としているのでRcvスレッド側で設定した wParam=0x1234 lpParam=0x5678 を取得するこ立できません。 どうも受信側で混乱しています。勘違いかご指摘いただければ幸いです。
お礼
Dodonpa2様 再三の回答ありがとうございます。 メッセージポンプに関する追求前に、メッセージ処理側でできる工夫はないかということで 1.処理をOnRcvSts(WPARAM wParam, LPARAM lParam)内に移す。 2.処理内容からwhile loopを追放。 工夫の結果、メッセージの変化をEventとしてOneScanのプログラムに書き換えることができました。 これによりメッセージポンプのアンバランスは解消したとみなしました。 3.さらに受信スレッドでハードから送り込まれた内容を調べ変化のあるときだけPostMessage()を発行するようにして、メッセージの数を数百分の1に減らした。 以上の処置で行けそうな感じです。 しかしこれは本格的な解決策とは言えません。この後時間を作ってお教えいただいた方法に挑戦してみます。 本当にお世話になりありがとうございました。