none
マルチメディアタイマーによるスレッドの呼び出しと他操作による影響 RRS feed

  • 質問

  • いつもお世話になっております、鏑木と申します。
    現在Visual Studio 2005にてVC++のプログラムを作成しており、ここ最近ずっと悩んでいる箇所が
    参考となるご意見を頂ければと思い、こちらへ久しぶりに投稿させていただきました。

    質問させて頂きたい内容は
    現在、ダイアログベースのプログラムでマルチメディアタイマーを使用して、20msec毎にダイアログ上のピクチャーコントロール
    へ波形を描画するプログラムを作成しております。描画する波形のデータはおなじマルチメディアタイマーにおいて、500msec(20msec×25)
    経過時にシリアル受信スレッドを呼び出し、受信スレッド内にてシリアル受信操作を行いデータを受信しています。

    そのまま何も操作しなければ順調にデータの受信、波形の描画は滞りなく進むのですが、ダイアログ上のボタンを選択することで別のダイアログを
    表示させたり、その表示させたダイアログを閉じたりした際に、アプリケーションに負荷がかかる為かマルチメディアタイマーが20msec毎の呼び出し
    ではなくなり(10msec以下になったりした場合もあった)、そのせいなのか500msec毎のシリアル受信スレッドにおいて、正常にデータが受信できなく
    なる、という症状がでております。
    なんとか他の操作をした際に、マルチメディアタイマーへの影響がなくならないか考えてみたのですがなかなかないいアイディアが思い浮かびません。

    そこで対応策として、シリアル受信時にデータが受信できないのは500msec毎にシリアル受信スレッドを呼び出し、そのスレッド内でシリアル受信操作
    をしているためではないかと考え、データ受信処理を500msec毎にスレッドを呼び出すのではなく、直接シリアル受信処理を呼び出すようにすると、
    その他の操作をしても、正常にデータを受信することはできました。しかし、シリアル受信操作内で、データが全て受信されるまでの待機操作などにより(Sleepの呼び出し)
    ダイアログ上のピクチャーコントロールへの波形の描画がスムーズに描画することが出来なくなり、コマ送りのような描画となってしまいました。
    そこで、描画の処理も別スレッドにしようかと思ったのですが、そうするとマルチメディアタイマー内にて20msec毎にスレッドを呼び出すことになるので、これでは初めの
    シリアル受信スレッドを500msec毎に呼び出すのと変わらないところか、もっとよくないだろうと思い断念しました。

    以上がこれまでの経過なのですが、文章を見ただけではかなり分かりづらいと思いますので、不明な点がありましたらなんなりとおっしゃって頂ければと思います。
    参考となるご意見がありましたら、どうぞよろしくお願い致します。
    開発環境がCE6.0という特殊な環境の為か、PC上よりもいろいろと動作に制約があることも原因の1つを担っているのかなとも思っております。

    開発環境はWindows CE 6.0です。
    2010年2月9日 3:22

回答

  • クリティカルセクションやInterLockというのはスレッドの排他処理用につかうということであってますでしょうか?
    スレッドの排他処理用、という言葉で合っているような、違うような、微妙な感じがしますが、クリティカルセクションの場合、InitializeCriticalSection でクリティカルセクションオブジェクトを作成し、バッファへのアクセス時に EnterCriticalSection を用いて、クリティカルセクションへ「入る」という感じになります。

    バッファへの書き込み関数の先頭に EnterCriticalSection、末尾に LeaveCriticalSection を呼び出す事で、基本的に 1 スレッドのみがバッファへの書き込み関数を実行出来る、という感じになります。

    クリティカルセクションに入れるのは基本的に 1 スレッドに限定され、EnterCriticalSection を用いた場合、クリティカルセクション内に入っているスレッドがあれば、そのスレッドが LeaveCriticalSection を呼び出すまで停止します。

    また、EnterCriticalSection の替わりに TryEnterCriticalSection を用いた場合、クリティカルセクションに入れなければ 0 を返します。この API を用いた場合、クリティカルセクションに入れるまで待つ、という動作にはなりません。

    この説明は API の CRITICAL_SECTION オブジェクトの話であって、MFC の CCriticalSection クラスとは違うので注意してください。こちらでは CCriticalSection よりも CSingleLock クラスを使うよう奨めています。

    また、API を直接使う、MFC 経由で使う場合、共に LeaveCriticalSection ( MFC ではおそらく Unlock ) を呼び出さないまま、スレッドが終了等した場合、おそらくどのスレッドもクリティカルセクションに入れない、という状態になるかと思いますので、お気を付けください。


    InterLock 系の API ですが、これはプロセス全体で volatile 指定をした LONG 型整数を扱う方法です。とは言え、どちらかと言えば、COM オブジェクトの参照カウンタなどに使う関数なので、バッファの排他処理には向かない様に思えてきました。ただ、クリティカルセクションよりも高速なので、リングバッファを作成し、読み込んだデータの先頭と末尾を識別するのにはいいかも知れません。


    # ただ、両方とも、ざっと見た限り、WinCE に付いて触れているドキュメントが見付かりませんでした・・・。クリティカルセクションがないなら、ミューテックスとなるでしょうが、カーネルオブジェクトでプロセス間使用可能・・・。つまり、クリティカルセクションより重たいものになりますね・・・。


    いずれにしても、どのバッファのどの部分をどのタイミングで排他制御するかは、デッドロックを招きかねない ( とは言え、排他制御しないとデータの順番が狂う可能性が増します・・・ ) ので、気をつけて下さい。


    他のダイアログというのは、メインダイアログと同じような作りなのですが、メインダイアログと同様に波形や数値の表示があり、それらはメインダイアログのタイマーイベントをそのまま使って表示させています。
    この他のダイアログというのは、メインダイアログを完全に覆うものなのでしょうか?
    であれば、表示中はメインダイアログの表示更新を止める ( ビットマップへの波形描画のみ行い、ビットマップそのものは再表示しない ) 等の方法が取れると思います。
    • 回答としてマーク 鏑木肆星 2010年2月26日 8:07
    2010年2月19日 14:47
  • 「他のダイアログの呼び出し」も、かなり複雑な状況ですね
    条件を色々とわけてテストし、問題の現象がおきる最小条件を絞るべきだと思います。

    モーダル・モードレスのどっちでおきるのか?どっちもか?
    「何も処理しないダイアログ」を出すだけでもおきるのか?
    それとも、ダイアログよりも表示のためのメモリ確保などが原因か?

    などなど

    • 回答としてマーク 鏑木肆星 2010年2月26日 8:07
    2010年2月22日 8:25
  • CE_OVERRUNが気になったので調べて見たんですが、
    CEってComm Errorの方なんですね。
    で、幾つかHomePageを見てみたんですが、
    COMってやっぱり結構厄介みたいですね。
    HW的な要因なんかもあるみたいです。

    一応、見つけたHPを紹介しておきます。

    Win32API シリアル受信プログラムデバッグの一例
     

    解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。
    • 回答としてマーク 鏑木肆星 2010年2月26日 8:07
    2010年2月22日 9:19
  • 大昔、フロー制御のできない機器(やれやれ)を相手にしたことがありますが大変でした。
    ちなみにスレ主さんの通信条件ってどうなってるんでしょう。やや気になります。
    • 回答としてマーク 鏑木肆星 2010年2月26日 8:07
    2010年2月23日 7:23

すべての返信

  • > 現在、ダイアログベースのプログラムでマルチメディアタイマーを使用して、20msec毎にダイアログ上のピクチャーコントロールへ波形を描画するプログラムを作成しております。

    まず、20ms毎の描画ということですが、これだと単純計算で50fpsになります。
    ここまでの描画間隔が必要なのでしょうか。

    > しかし、シリアル受信操作内で、データが全て受信されるまでの待機操作などにより(Sleepの呼び出し)ダイアログ上のピクチャーコントロールへの波形の描画がスムーズに描画することが出来なくなり、コマ送りのような描画となってしまいました。

    データとの待ち合わせをどのようにしているのか分かりませんが、(待ち合わせ時間は知りませんが)Sleepで待ち合わせるのが問題なのかもしれません。
    WaitForMultipleObjects/WaitForSingleObjectで待ち合わせてみてはどうでしょう。
    実際には待ち合わせる必要がなければ(20ms x 5のタイミング±20msが許されるなら)、500msの受信スレッドが受信したデータをデータバッファに(補正できるから時間付きで入れると安心なのかな)に入れて、このバッファアクセスを排他制御するようにしておけば、20msの描画スレッドが止まりにくいと思います。
    20msの描画スレッドでは、前回描画部+今回描画部でBLTすれば高速で描画できるだろうし。ずれた分は、バッファの時間で調整すれば、なんとかいけそうな感じもしますけど。
    2010年2月9日 4:32
  • CEは不案内なので、一般論です。

    1.システムの構成が「親アプリ」「受信スレッド」「表示Win」「スケジューラ」に分かれていて、「受信スレッド」は
      「表示Win」ではなく、「親アプリ」から起動されているほうが何かと便利です。

    2.「受信スレッド」は受信イベントで受信しFIFOにデータを詰め込むだけの単純作業を繰り返します。
      受信モードに入ったら完全停止させる以外には、「受信スレッド」をサスペンドしたりしないことが重要です。
      FIFOのサイズは、「表示Win」の最大表示可能時間内に発生しうるデータ数に、余裕を加味して決めます。

    3.「スケジューラ」はMMTimerを持ち、一定時間経過したら、「表示Win」に対して、表示させたい時刻を指定して
      再描画を促します。この命令はメッセージキューを介さない方法で、できたらダイレクトに「表示Win」の
      当該処理用関数をを呼ぶほうが良いでしょう。また、人間が不自然さを感じない程度でシステムの負荷と
      相談しながら描画更新インターバルを決めます。 多分50[ms]~500[ms]程度でしょう。
      また、表示させたい時刻とは、最新データの時刻とは限りません。スムーズに表示するためには、
      ある程度FIFOにバックログがないと調整が利かなくなります。

    4.「表示Win」は指定された時刻より過去のデータをFIFOから取得して、描画用データを算定してバッファします。
      自分自身をInvalidate()して、すぐUpdateWindow()します。

    5.「表示Win」はWM_PAINTで現存する算定済み描画用データを使ってグラフを表示します。

    まぁ、こんな感じにできてれば、大丈夫だと思いますけど。
    全体的に処理が細分化している理由は、いわゆるBigJobにならないように処理を分割して、
    メッセージキューが滞りなく流れるように考慮しているわけです。
    「スケジューラ」をインテリジェンスにすると、突然の大量受信対策もできますし、
    こった作りにすることもできます。

    2010年2月9日 5:21
  • ふるかわあきひと様
    >まず、20ms毎の描画ということですが、これだと単純計算で50fpsになります。
    >ここまでの描画間隔が必要なのでしょうか。
    外部から500msec分25データを一度に500msec毎に受信するため、受信したデータを波形として描画する際に20msec毎のびょうが間隔となります。

    シリアル受信処理は以下のようにしております

    while(1){
     if(要求分のデータが受信されたら)
        break;

     ClearCommError(m_hComm,&dwErrors,&ComStat);
     dwCount = ComStat.cbInQue;
     if(dwCount == 0){
       Sleep(10);
       continue;
     }

     ::ReadFile(m_hComm,recv,dwCount,&dwRead,NULL);
     if(int i=0 ; i<dwCount ; i++){
       recv_data[count++] = recv[i];
     }
    }
    のような処理となっております。上のコードでのrecv_dataが受信データを格納する配列です。この中で待ち合わせとしてSleep
    をよんでいます。

    描画方法についてはBLTとしていますが、20msec毎にスレッドを作成して、描画をさせるのはどうかなと思っております。
    ですがやはり、受信スレッド、描画スレッドは別々のスレッド、別々のマルチメディアタイマーにすべきでしょうか?
    2010年2月9日 7:01
  • 中澤@失業者様、ご回答ありがとうございます。
    >システムの構成が「親アプリ」「受信スレッド」「表示Win」「スケジューラ」に分かれていて、「受信スレッド」は
    >「表示Win」ではなく、「親アプリ」から起動されているほうが何かと便利です。
    なるほどです。ダイアログベースのプログラムなのですが、この場合の親アプリと表示Winはどう違ってくるのでしょうか?

    受信についてですが、受信イベントを待っての受信方法ではなく、こちらから500msec毎に外部へ受信要求をしなければならないです。
    やはり、描画と受信は別々のスレッドであるべきなのでしょうか?現状ではメインスレッドからの同じマルチメディアタイマーで呼び出しているのですが、
    これをそれぞれ別スレッドからのマルチメディアタイマーで呼び出したほうがよいのでしょうか?
    とにかく描画と受信処理をお互い干渉させないようにしたいのですが、どうにも分かりません。
    2010年2月9日 7:08
  • 受信についてですが、受信イベントを待っての受信方法ではなく、こちらから500msec毎に外部へ受信要求をしなければならないです。
    やはり、描画と受信は別々のスレッドであるべきなのでしょうか?現状ではメインスレッドからの同じマルチメディアタイマーで呼び出しているのですが、

     まず、自分の言う「表示Win」とは、おそらくメインDLGの子ウインドウであり、グラフを表示するコントロールで
    あるとの認識です。この構成になっていないと、メインDLG全体にInvalidateしなければならなくなり、
    結果、表示更新が無用なボタンだの、エディットなども再描画の対象になってしまい、極めて非効率です。
    これらコントロールの再描画は時間的に無視できないほどに高コストであると認識すべきです
    (見た目上はわかんないけど)。
     当然この「表示Win」は裏バッファ(BMPを選択したメモリーDC)方式であるべきで、これ以外の選択は
    DirectXしか考えられません(CEで使えるかどうかは知らないので突っ込まないでください)。

     次に受信についてですが、相手にリクエストが必要な場合も含めて、「受信スレッド」が全責任を負うべきで、
    メインからは受信の「開始」と「停止」の口を残すぐらいにしといた方かシンプルに組めるでしょう。
    この口もイベントを使うほうが良いかもしれません。

     タイマーについてですが、独立性を上げるためには各々のスレッドが必要とするタイマーを
    保持すべきです。外部からの割り込みは無いほうがデバッグが楽です。

     また、自分の文中のFIFOは当然リングバッファ形式で、こいつのつくりがパフォーマンスに影響します。
    生の受信データのFIFOと表示用に座標計算したデータのバッファが必要です。

    2010年2月9日 8:46
  • まず、まとめると
    ・500ms毎にデータを読み込む必要があり、ここで読み取るデータは20ms毎の25セットのデータである
    ということになるのでしょうか。
    そう考えると50fpsで描画する必要もないのではないでしょうか。

    20ms毎の描画スレッドは、WaitForSingleObjectなどの20msタイムアウトで、メモリDCへの書き込みとBLTが中心となると思います。
    500msのデータ読み込みスレッドは、WaitForSingleObjectなどの500msタイムアウトで、データの読み込みになると思います。
    これらの2つのスレッドを時間タグ付きのデータバッファを排他で制御して読み書きすれば、それほどカクカクしないと思いますがどうでしょう。

    マルチメディアタイマーて、コールバックを処理中に抜け出せなければ、処理中にも関わらず次のコールバックが呼び出されることがあったような気もします。

    2010年2月10日 2:17
  • お久しぶりです。が、回答ではなく、確認したい事があって書き込みさせて頂きます。

    まずは、WinCE でもマルチメディアタイマーが使えるようになったようで、何よりです。

    以前のご質問の際に、GUI スレッドは色々なところから足を引っ張られる、Windows はリアルタイム OS ではない、描画が多少遅れても愛嬌で済むが、データの取りこぼしはそうは行かない、簡単に乱れるタイマー間隔よりは経過時間で管理した方が良い等、皆さんから回答があったと思いますが、今の状態はどうなっているのでしょうか?

    仲澤@失業者 さんが回答なされているように、リングバッファを用いる事で、かなり時間的な余裕は出来ると思いますが、そもそものデータの出所が固定長で、0.5 秒おきに上書きされてしまう仕組みなんでしたっけ?
    2010年2月10日 5:01
  • ふるかわあきひと様ご回答ありがとうございます。

    教えて頂いたように、描画スレッドをWaiForSingleOjectの20msecタイムアウト、受信スレッドをWaitForSingleObjecの500msecタイムアウトで
    各処理関数を呼び出し、マルチメディアタイマーを使用しないようにしたところ、やはり500msec毎の受信処理内の待機処理によって、500msec
    毎に波形の描画が一時停止するように動作します。
    かなり初歩な質問になってしまうのですが、受信スレッド内で、WaitForSigleObjectで500msec待機したあとにSendMessageでメッセージを送信し、
    そのメッセージ受信箇所でデータ受信処理をしているのですが、これではメインスレッドと別のスレッドで受信処理していることにならないのでしょうか?
    ちなみにメッセージの送信先はメインスレッドで、受信処理もメインスレッド内のメンバー関数としているのですが。。

    一番初めに説明させていただきましたシリアル受信処理での正常にデータ受信できなくなる原因は、どうもClearCommErrorにてCE_OVERRUNが
    発生してる為のようです。
    2010年2月10日 8:11
  • ミッヒー様、ご回答頂きありがとうございます。

    CEにてマルチメディアタイマーをどうにか使用することはできたのですが、呼び出せる箇所がメインスレッドのOnInitDialog()内などと限定されているようです。
    現状の描画としては、初めの質問にあるように1つのマルチメディアタイマー(20msec毎)でデータ受信と描画をまかなっており、受信データ用のバッファとしては
    500msec毎に上書きはされるのですが、バッファを2つ準備し、1つのばっふぁに受信データ書き込み時にはもう1つのバッファ内のデータを波形として出力
    するようにしています。

    どうもシリアル通信エラーの原因が、データ受信処理を別スレッドで呼び出している状態で、メインスレッドに負荷がかかったときに、シリアルデータ受信の
    ClearCommErrorにてCE_OVERRUNが発生したために、データが正常に受信できなくなっている為のようです。
    これが受信処理を別スレッドにしない?ければ、同じように負荷をかけてもデータは受信できるようです。

    自分でもわけがわからず、説明文もかなりわかりづらくなっており申し訳ございません。
    2010年2月10日 8:17

  • ClearCommError が CE_OVERRUN を返しているという事は、バッファのオーバーランですよね。

    ::ReadFile(m_hComm,recv,dwCount,&dwRead,NULL);
    この recv 配列のバイトでのサイズと dwCount 値は一致してますか?
    また、要求した読み込みデータバイト数 dwCount と、実際に読み込まれたバイト数の dwRead はどうでしょうか?
    2010年2月10日 9:03
  • ミッヒー様、ご回答ありがとうございます。

    そうなんです、バッファのオーバーランのようなのです。
    通常時にはこのようなことは発生しないのですが、外部との送受信するデータ数を増やし(印刷時など)、
    その状態で他のダイアログ等を開いたり、閉じたりをわざと何度も繰り返したりしているとこのエラーが
    発生します。

    >この recv 配列のバイトでのサイズと dwCount 値は一致してますか?
    recv配列は512バイトとし、dwCountはClearCommErrorからの取得したものなのですが、最大で
    256バイトとしています。これは一致させなければならないのでしょうか?

    >また、要求した読み込みデータバイト数 dwCount と、実際に読み込まれたバイト数の dwRead はどうでしょうか?
    エラー発生直前までdwCountとdwReadは同じ値となっていました。
    2010年2月10日 10:00
  • recv配列は512バイトとし、dwCountはClearCommErrorからの取得したものなのですが、最大で256バイトとしています。これは一致させなければならないのでしょうか?
    recv配列のサイズと、読み込むサイズを一致させる必要はない ( 当然、recv 配列の方が小さいと駄目ですが ) と思いますが、残念ながら Comm 系の API を使った経験がないので・・・。

    ところで、dwCount は ClearCommError により COMSTAT.cbInQue に格納された値ですよね。この値が 256 バイトをも超え、512 バイトより大きくなっている事はないでしょうか?

    他の処理で ReadFile 呼び出しが行えず、その間に蓄積された COMSTAT.cbInQue のサイズが 512 バイトを超えてしまっている、とか。

    GetCommProperties で取得できる COMPROP.dwCurrentTxQueue の値は 512 より大きかったりしませんか?

    # 使った事のない API 群なので、予想ばかりで的確なアドバイスが出来ず、すいません・・・。
    2010年2月10日 10:47
  • ミッヒー様、ご回答ありがとうございます。

    >ところで、dwCount は ClearCommError により COMSTAT.cbInQue に格納された値ですよね。この値が 256 バイトをも超え、512 バイトより大きくなっている事はないでしょうか?
    調べてみましたが、cbInQueの値は512バイトを超えることはありませんでした。

    >他の処理で ReadFile 呼び出しが行えず、その間に蓄積された COMSTAT.cbInQue のサイズが 512 バイトを超えてしまっている、とか。
    ClearCommErrorのCE_OVERRUNについて調べてみると、シリアル受信処理よりも優先度の高い割り込み処理が発生し、その間に受信が滞ってしまい、バッファのサイズをオーバーし、
    エラーが発生するようです。
    なので、シリアル受信処理の時に、そのような割り込みが発生しないように出来ればなと思っているのですが。
    ちなみに、ClearCommErrorを用いてあらかじめ受信するデータを確保してからのデータ受信ではなく、ReadFileのみでの受信であれば、受信する時間はかかりますが、エラーは
    発生しないようです。、

    2010年2月10日 11:11
  • 上のコードでのrecv_dataが受信データを格納する配列です。この中で待ち合わせとしてSleepをよんでいます。

     タイミングを計るために Sleep を使ってはいけません。Sleep は、待つための命令ではありません。他のスレッドに処理を回すための命令です。Sleep(10) としたからといって、10ミリ秒後に復帰する保障はありません。しかし、最低10ミリ秒は復帰しないように、実装されているはずです。


    Jitta@わんくま同盟
    2010年2月10日 12:18
  • Jitta様
    ご回答ありがとうございます。
    タイミングをはかるためにSleepを使用するのはよくないということは、やはり::ReadFile関数で指定バイトを
    受信する方法が最良なのでしょうか?幸い、受信するデータのバイト数は分かるためその方法も受信は可能
    なのですが。。
    2010年2月14日 2:40
  • 様々な要素が絡んでいるように思われるので、よくはわかりません。「~の方がよいのか?」と聞かれても、判断はできません。

    50fps というのが、「データがあるから表示しなければならない」のか、それだけの精度が必要なのか、わかりません。人間の目に連続して見える様にするだけなら、30fpsもあれば十分です(昔のテレビ アニメは24fps)。この仕様の元になった要求は、何でしょう?(答えを書いてくださいというのではありません。要求元に尋ねて、その回答によって50fpsでなければならないのか、判断をしてください。2/9 7:01 の返答では、「データがあるから描く」のように思われるので。)

    データを受け取るのは、インターネット経由なのか、イントラネットのみ経由なのか、その他なのか。20msecのデータを25個まとめて送ってくるのはわかりましたが、そのデータが500msecごとに遅滞なく受信できるのでしょうか。また、データ量がわからないのですが、どれくらいの時間で受信できるのでしょう?(これも、答えて欲しいのではなく、仕様を決める要素です、ということ。)
    Jitta@わんくま同盟
    2010年2月14日 5:17
  • ちなみに、ClearCommErrorを用いてあらかじめ受信するデータを確保してからのデータ受信ではなく、ReadFileのみでの受信であれば、受信する時間はかかりますが、エラーは
    発生しないようです。、
    え~と、意味わかりません。「ClearCommErrorを用いてあらかじめ受信するデータを確保してからの」とは
    どういうコードでしょうか。

    一般にRS-232Cのデバイスドライバのバッファのサイズは SetupComm() を使いますよねぇ。
    また、ReadFile()以外でRS-232Cの受信データを取得する方法ってなんでしょう?
    字面に何かすごく違和感を感じたのですが・・・。
    2010年2月15日 4:40
  • 仲澤@失業者さん、ご回答ありがとうございます。

    かなり検討違いなことを言ってしまったようで申し訳ございません。
    ClearCommErrorを用いて、受信するデータを確保するのではなく、現在シリアルポートの入力バッファにバッファされているデータサイズを取得する
    ですね(笑)このときのClearCommError呼び出し時に、CE_OVERRUNが発生するために、その先にデータ受信処理ReadFileの読み込みが失敗
    するので、このClearCommErrorを呼ばずにただReadFileと呼ぶと、とりあえずは指定バイト分のデータは受信できるということです。。
    これでは解決にならないんですけどね。。

    仲澤@失業者さんに教えて頂いたSetupComm()にて試しに、入力バッファのサイズを大きくしてみましたが症状は収まりませんでした。

    とりあえずデータ受信処理時のデータ数を調整して、受信処理時の負荷を減少させようかと思っています。
    2010年2月15日 8:09
  • SendMessageで、UI描画がブロックされているということは考えられませんか。
    500msの読み込みスレッド内で完結して読み込んだ方が良いかもしれませんね。
    2010年2月15日 8:42
  • かなり初歩な質問になってしまうのですが、受信スレッド内で、WaitForSigleObjectで500msec待機したあとにSendMessageでメッセージを送信し、
    そのメッセージ受信箇所でデータ受信処理をしているのですが、これではメインスレッドと別のスレッドで受信処理していることにならないのでしょうか?
    ちなみにメッセージの送信先はメインスレッドで、受信処理もメインスレッド内のメンバー関数としているのですが。。
    別スレッドで処理していることにはならない
    「メインスレッドが、メッセージをうけて、受信処理をしている」 
    「サブスレッドは、送ったメッセージの返答待ちをしている(SendMessage)」


    メインスレッドのさす意味が「ダイアログベースの本体のダイアログ」であるなら、
    受信処理・表示処理の両方ともメインスレッドで行っている。

    さらに、他のダイアログを開いたり閉じたりするために、メインのダイアログにつけたボタンで操作などをしてたら、
    これらもふくめて、「全部一つのスレッドで順番に処理」してることになってると思います。


    ♯ところで、マルチメディアタイマーが使えるということは timeGetTime関数は使えますでしょうか?
    ♯MSDNによるとCE5.0から使えそうな感じですが
    • 編集済み Ap_KNP 2010年2月16日 0:56 追記
    2010年2月15日 13:13
  • かなり初歩な質問になってしまうのですが、受信スレッド内で、WaitForSigleObjectで500msec待機したあとにSendMessageでメッセージを送信し、
    そのメッセージ受信箇所でデータ受信処理をしているのですが、これではメインスレッドと別のスレッドで受信処理していることにならないのでしょうか?
    ちなみにメッセージの送信先はメインスレッドで、受信処理もメインスレッド内のメンバー関数としているのですが。。
    お久しぶりです、PATIOです。
    既に他の方も回答されていますが、受信処理を別のスレッドにしないと描画と受信処理が同じスレッド内になってしまうので
    受信処理が行われている間は描画処理が出来ない事になります。
    再描画間隔が長ければこれでも何とかなるのかもしれませんが、現在のような厳しい間隔で再描画を行なっているのであれば、
    受信処理中は描画処理が出来ませんからその間は描画が止まります。
    基本的に一つのスレッドで二つの処理を同時に行う事はできません。
    ですから描画と受信処理を同時に行う必要があるのであれば、受信処理は別のスレッドに追い出す必要があります。
    これはマルチスレッド云々と言うよりも処理を組む時の基本的な考え方になります。
    メインスレッドに画面の描画を任せるのであれば、それ以外の重い処理は別スレッドに追い出すか、
    処理をコマ切れにして描画を頻繁に行なえるように制御を頻繁にOSに戻す必要があります。

    受信処理は一般的に重い処理だと思いますから描画を行うメインスレッドに置くべきでは有りません。
    思うのですが、描画用のマルチメディアタイマーと受信用のマルチメディアタイマーの管理は別に行うべきではないかと思います。
    現在の受信スレッドと呼んでいる物は実際には受信用のタイマースレッドになってしまっています。
    そうではなく、受信処理を行うスレッドを別に起こし、受信スレッド内でマルチメディアタイマーも管理、監視します。
    500ms待機後の処理も同じスレッド内で行ないます。
    描画用のマルチメディアタイマーは別に管理してそちらからのイベントで描画処理を行います。

    描画処理に対して極力影響を与えない為には、描画処理以外の重い処理は別のスレッドにする必要があります。
    但し、スレッド側の処理があまりにも思い場合はメインスレッド側も影響を受けると思いますので、完全に影響が無い状態と言うのは
    無理だと思います。特にHW環境がプアなCEの場合、この傾向は強くなると思います。
    PCのようにCPUが強力な環境だと力技で何とかなってしまう事も多いのですけれど。

    追記
    ふと思ったんですが、受信と描画でマルチメディアタイマーを分けるのであれば、
    受信データをおいておくバッファーはダブルバッファにしておいたほうが良いかもしれません。
    そうすれば、書き込み処理で描画側が待たされる事は無いはずなので。
    同じバッファを受信と描画に使っていると描画時にバッファを参照している最中に
    受信側がバッファを書き換えてしまうかもしれません。
    通常ならここで排他処理を入れるところですが、そうなると後からの処理が待たされる事になります。
    ダブルバッファにしておいて描画側が参照しているバッファと受信処理を行うバッファが
    被らないように切り替えて処理すれば、お互いに干渉しないで済みそうです。
    描画内容の整合性等でもう少し制御した方が良いかもしれませんけれど。

    解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。
    • 編集済み PATIO 2010年2月16日 1:41 追記
    2010年2月16日 1:34
  • ふるかわあきひと様、ご返答が遅くなり申し訳ございません。

    みなさんのご意見を頂きまして、各スレッド内で完結して処理(メッセージをとばさずに処理)したところ、
    波形の動きの一時停止はされなくなったのですが、各ダイアログの呼び出し時にはやはり受信処理に
    影響がでてしまい、データ受信時にオーバーランエラーがでてしまうようです。
    また、スレッドでのWaitForSingleObject()による待機ですと、WaitForSingleObject関数を呼び出してから
    指定時間待機します。今回目的の動作としては、指定時間毎に受信処理関数、波形描画関数を呼び出したい為
    、WaitForSingleObject関数呼び出しからの待機ではこれらの処理関数の処理時間分だけ、次の処理関数
    を呼び出す時間がずれてしまうため目的の動作をはたないのではないかなと思っております。
    2010年2月18日 1:28
  • yominet様、ご回答ありがとうございます。

    ご返答が遅くなり申し訳ございません。

    >別スレッドで処理していることにはならない
    >「メインスレッドが、メッセージをうけて、受信処理をしている」 
    >「サブスレッドは、送ったメッセージの返答待ちをしている(SendMessage)」
    なるほど、そうだったんですね。大変勉強なります、ありがとうございます。

    >メインスレッドのさす意味が「ダイアログベースの本体のダイアログ」であるなら、
    >受信処理・表示処理の両方ともメインスレッドで行っている。
    その通りです。メインスレッドは本体ダイアログのことを指しております。説明不足で申し訳ございません。

    >さらに、他のダイアログを開いたり閉じたりするために、メインのダイアログにつけたボタンで操作などをしてたら、
    >これらもふくめて、「全部一つのスレッドで順番に処理」してることになってると思います。
    他のダイアログの呼び出しというのも、ダイアログに張り付けたピクチャーコントロールの選択での呼び出しなので、
    本体のダイアログからの呼び出しとなりますね。

    そこでサブスレッド内でメインスレッドへメッセージを送信せずに、スレッド内で処理をするように変更したところ、
    受信処理による波形処理の一時停止をする動作はみられなくなりました。
    しかし、サブダイアログ呼び出しを何度か繰り返しているとやはり、データ受信時にオーバーランエラーが発生して
    しまうようです。
    データ受信処理をメインスレッドの動作にすればオーバーランエラーは必ず発生しないのですが。。。

    >♯ところで、マルチメディアタイマーが使えるということは timeGetTime関数は使えますでしょうか?
    >♯MSDNによるとCE5.0から使えそうな感じですが
    確かめてみたところ、timeGetTime関数は使うことが出来るようです。
    2010年2月18日 1:41
  • PATIO様、ご回答ありがとうございます。

    本当にお久しぶりです、ご返答が遅くなり申し訳ございません。

    >既に他の方も回答されていますが、受信処理を別のスレッドにしないと描画と受信処理が同じスレッド内になってしまうので
    >受信処理が行われている間は描画処理が出来ない事になります。
    >再描画間隔が長ければこれでも何とかなるのかもしれませんが、現在のような厳しい間隔で再描画を行なっているのであれば、
    >受信処理中は描画処理が出来ませんからその間は描画が止まります。
    別スレッド内でメインスレッドにたいしてメッセージを送信した場合は別スレッドの処理にならないということは、データ受信も波形描画
    も同じスレッド内になってしまうので、PATIO様のおっしゃる通り描画が止まってしまうのですね。

    >ですから描画と受信処理を同時に行う必要があるのであれば、受信処理は別のスレッドに追い出す必要があります。
    >これはマルチスレッド云々と言うよりも処理を組む時の基本的な考え方になります。
    受信処理はやはり別スレッドにすべきですね。。それは重々分かってはいるのですが。。
    受信処理を別スレッドにすると、今回のようにデータ受信処理時にオーバーランエラーが発生しますが、なぜか受信処理もメインスレッド
    にしてしまうとデータ受信処理時にオーバーランエラーは発生しません。不思議です。。

    >思うのですが、描画用のマルチメディアタイマーと受信用のマルチメディアタイマーの管理は別に行うべきではないかと思います。
    >現在の受信スレッドと呼んでいる物は実際には受信用のタイマースレッドになってしまっています。
    >そうではなく、受信処理を行うスレッドを別に起こし、受信スレッド内でマルチメディアタイマーも管理、監視します。
    >500ms待機後の処理も同じスレッド内で行ないます。
    >描画用のマルチメディアタイマーは別に管理してそちらからのイベントで描画処理を行います。
    おっしゃる通り、受信スレッドが受信用のタイマースレッドになっていますね。
    それでは、まずデータ受信用スレッドと波形描画用スレッドを作成し、各スレッド内にてそれぞれのマルチメディアタイマーを呼び出し、
    そのマルチメディアタイマーから呼び出されるコールバック関数内にてたとえばイベントをシグナル状態にし、それをみて、各スレッド内
    にあるデータ受信処理、波形描画関数処理を呼び出す、ということでよろしいでしょうか?
    ちょっと試してみたいと思います。
    もしかしたらですが、これがCEのせいなのかは分からないのですが、マルチメディアタイマーをどこでも呼び出すことができるわけではなく、
    決まって位置(OnInitiDialog()内とかでしかどうも呼び出せないようなのです。それ以外の箇所で呼び出すと、なぜかフリーズしてしまいます。。

    >ふと思ったんですが、受信と描画でマルチメディアタイマーを分けるのであれば、
    >受信データをおいておくバッファーはダブルバッファにしておいたほうが良いかもしれません。
    PATIO様のおっしゃる通りのデータの影響を考慮して、現在はダブルバッファでデータを格納しており、受信したデータを格納するバッファと
    波形の描画に使用するバッファは別としています。
    そのために一応現状では1つのマルチメディアタイマーからデータ受信処理(500msec = 20mese × 20毎)、波形描画処理(20msec毎)
    を呼び出して同期をとるようにしております。

    追記:
    波形描画処理も別スレッドにするということは、描画処理をするピクチャーコントロールクラスは静的メンバ関数にするしか
    ないわけですよね。私の勝手な印象なのですが、あまり静的にクラスを呼び出すというのはよくない印象をもっているのですが。。
    • 編集済み 鏑木肆星 2010年2月18日 2:11 追記を追記
    2010年2月18日 2:02
  • 受信スレッドで500msec毎の動作をさせる方法案

    1:イベントを使う
    受信スレッドでは、WaitForSingleObjectで「時間を待つ」のではなく、
    「イベントがシグナルになるまで待つ」をしてください。
    今までどおり、メインスレッドで時間を待ち、指定時間になったらイベントをシグナルにします。


    2:timeGetTimeを使う
    たとえばこんな感じ

    int start = timeGetTime();

    while(1){

     if( (timeGetTime()-start)< 500 )continue;
     
     start = start + 500;  //比較時間を更新

     //受信処理

    }

    XPなら、timeBeginPeriodを使うとtimeGetTimeで得られる性能が1msecまで分解できる(CEはわからない)

    2010年2月18日 3:27
  • yominet様、ご回答ありがとうございます。

    教えて頂いた方法案は1,2の2種類の方法がとりあえず考えられるということでよろしいでしょうか?
    とすると2の方法案のwhile文による永久ループの処理も受信スレッドとして呼び出し、WaitForSingleObject関数
    呼び出しによる呼び出しの時間差をなくすということですね。
    そして1の方法案は、受信処理呼び出し用の受信スレッドをはじめに作成してイベントシグナル待機状態にし、メインスレッドから呼び出したマルチメディアタイマー
    の時間毎にイベントをシグナルにし、受信スレッド内で受信処理を呼び出させるということですね。
    1の方法案についてはPATIO様も同様のご意見をしてくださっていたので、まずは1の方法案から試してみたいと思います。
    2010年2月18日 5:01
  • 教えて頂いた方法により
    ・波形描画処理、データ受信処理を別スレッドで動作
    ・マルチメディアタイマーによって直接処理を呼び出すのではなく、イベントをシグナル状態にし、別スレッドであらかじめ
    呼び出していた処理を呼び出す

    ようにプログラムを作成したところ、結果として波形の通常表示時に一時停止などは発生しませんでした。
    しかし、マルチメディアタイマーによるタイマー処理をしているためか、別のダイアログを表示した際に、マルチメディアタイマーの
    時間間隔がずれてしまい、それに付随してイベントをシグナル状態にする時間もずれてしまう影響で、ダブルバッファでのデータ
    受信と波形描画に使用する配列の同期がときどきとれなくなる現象が発生しました。
    そこで、これは以前に作成していた方法なのですが、波形の描画の処理は別スレッドにせずに、メインスレッドで処理し、データ受信
    処理の呼び出しはイベントをシグナル状態にしての呼び出しではなく、その都度500msec毎に受信処理用スレッドを呼び出すことで
    他のダイアログを表示させてもダブルバッファの同期はとれているようです。

    また、教えて頂いた処理の呼び出しかたでも、メインダイアログ上のコントロールの操作による他のダイアログの呼び出しを何度か
    繰り返しますと、ClearCommError()にてCE_OVERRUNエラーが発生してしまいます。
    そこで、受信するデータ量を少なくしたところCE_OVERRUNエラーは発生しなくなりました。
    2010年2月18日 8:59
  • えーっと、少し整理させて下さい。

    全体の作りとしては、

    1. UI ダイアログ ( プライマリスレッド ) にグラフを表示
        マルチメディアタイマーの起動、タイマー通知関数もこのスレッド

    2. データ受信用スレッド
        500 ms 毎にデータを受信

    3. UI ダイアログから表示されるダイアログ × 2
        ( プライマリスレッド? )

    という感じでしょうか?

    1. のマルチメディアタイマーを 20 ms で作動させ、timeGetTime または GetTickCount で 500 ms 経過していれば、2. のスレッドが待っているイベントをアクティブにし、データを受信する、という処理かと思いますが・・・。

    同期処理に付き物の、バッファの排他処理はどうなっているのでしょうか?

    クリティカルセクションや InterLock 系などを使用しているのか、バッファの排他処理は行っていないのか、どうでしょうか?
    2010年2月18日 11:51
  • 確認したいことあります。

    他のダイアログとはどんなものでしょうか?
    生成し方(関数名なども)や、処理してる内容、またダイアログを閉じたときにする処理も。

    2010年2月18日 13:25
  • ミッヒー様ご回答ありがとうございます。

    おっしゃる作りで概ねあっております。
    ただ、3.のUIダイアログから表示されるダイアログは2個ではなく、複数あり、これらのダイアログの呼び出しはプライマリスレッド
    です。
    現状ではバッファの排他処理は行っていません。というのも排他処理の関係で、波形の描画の動きに影響がでないかなと
    思っておりまして。でも、バッファの排他処理をしてみたいと思います、ご意見ありがとうございます。

    クリティカルセクションやInterLockというのはスレッドの排他処理用につかうということであってますでしょうか?
    これらも現状ではつかっておりません。
    2010年2月18日 23:45
  • yominet様、ご回答ありがとうございます。

    他のダイアログというのは、メインダイアログと同じような作りなのですが、メインダイアログと同様に波形や数値の表示があり、
    それらはメインダイアログのタイマーイベントをそのまま使って表示させています。そのためメインダイアログのメンバ変数に
    他のダイアログのクラスを宣言し、その他ダイアログクラスのpublicのメンバ変数に波形を表示するピクチャーコントロールを作成し、
    それを呼び出して波形表示の処理をさせています。どのダイアログがアクティブかはフラグを作成し、そのフラグの値によってアクティブ
    なダイアログの判断をしています。


    ダイアログの呼び出し方は二種類あって、あらかじめ初めにモーダレスで呼び出したダイアログをShowWindow(SW_SHOW)する
    呼び出し方と、新たにモーダルでダイアログを呼び出すダイアログがあります。いずれのダイアログも上記のような波形・数値の表示
    をメインダイアログで処理しています。

    モーダルで呼び出したダイアログを閉じるときの処理は、newで作成したピクチャーコントロールのdelete等を行い、ダイアログアクティブフラグを
    変更しています。モーダレスで呼び出したダイアログを閉じるときの処理は、ダイアログアクティブフラグをだけを変更し、そのままダイアログの
    非表示を行います。
    2010年2月19日 0:05
  • クリティカルセクションやInterLockというのはスレッドの排他処理用につかうということであってますでしょうか?
    スレッドの排他処理用、という言葉で合っているような、違うような、微妙な感じがしますが、クリティカルセクションの場合、InitializeCriticalSection でクリティカルセクションオブジェクトを作成し、バッファへのアクセス時に EnterCriticalSection を用いて、クリティカルセクションへ「入る」という感じになります。

    バッファへの書き込み関数の先頭に EnterCriticalSection、末尾に LeaveCriticalSection を呼び出す事で、基本的に 1 スレッドのみがバッファへの書き込み関数を実行出来る、という感じになります。

    クリティカルセクションに入れるのは基本的に 1 スレッドに限定され、EnterCriticalSection を用いた場合、クリティカルセクション内に入っているスレッドがあれば、そのスレッドが LeaveCriticalSection を呼び出すまで停止します。

    また、EnterCriticalSection の替わりに TryEnterCriticalSection を用いた場合、クリティカルセクションに入れなければ 0 を返します。この API を用いた場合、クリティカルセクションに入れるまで待つ、という動作にはなりません。

    この説明は API の CRITICAL_SECTION オブジェクトの話であって、MFC の CCriticalSection クラスとは違うので注意してください。こちらでは CCriticalSection よりも CSingleLock クラスを使うよう奨めています。

    また、API を直接使う、MFC 経由で使う場合、共に LeaveCriticalSection ( MFC ではおそらく Unlock ) を呼び出さないまま、スレッドが終了等した場合、おそらくどのスレッドもクリティカルセクションに入れない、という状態になるかと思いますので、お気を付けください。


    InterLock 系の API ですが、これはプロセス全体で volatile 指定をした LONG 型整数を扱う方法です。とは言え、どちらかと言えば、COM オブジェクトの参照カウンタなどに使う関数なので、バッファの排他処理には向かない様に思えてきました。ただ、クリティカルセクションよりも高速なので、リングバッファを作成し、読み込んだデータの先頭と末尾を識別するのにはいいかも知れません。


    # ただ、両方とも、ざっと見た限り、WinCE に付いて触れているドキュメントが見付かりませんでした・・・。クリティカルセクションがないなら、ミューテックスとなるでしょうが、カーネルオブジェクトでプロセス間使用可能・・・。つまり、クリティカルセクションより重たいものになりますね・・・。


    いずれにしても、どのバッファのどの部分をどのタイミングで排他制御するかは、デッドロックを招きかねない ( とは言え、排他制御しないとデータの順番が狂う可能性が増します・・・ ) ので、気をつけて下さい。


    他のダイアログというのは、メインダイアログと同じような作りなのですが、メインダイアログと同様に波形や数値の表示があり、それらはメインダイアログのタイマーイベントをそのまま使って表示させています。
    この他のダイアログというのは、メインダイアログを完全に覆うものなのでしょうか?
    であれば、表示中はメインダイアログの表示更新を止める ( ビットマップへの波形描画のみ行い、ビットマップそのものは再表示しない ) 等の方法が取れると思います。
    • 回答としてマーク 鏑木肆星 2010年2月26日 8:07
    2010年2月19日 14:47
  • 「他のダイアログの呼び出し」も、かなり複雑な状況ですね
    条件を色々とわけてテストし、問題の現象がおきる最小条件を絞るべきだと思います。

    モーダル・モードレスのどっちでおきるのか?どっちもか?
    「何も処理しないダイアログ」を出すだけでもおきるのか?
    それとも、ダイアログよりも表示のためのメモリ確保などが原因か?

    などなど

    • 回答としてマーク 鏑木肆星 2010年2月26日 8:07
    2010年2月22日 8:25
  • CE_OVERRUNが気になったので調べて見たんですが、
    CEってComm Errorの方なんですね。
    で、幾つかHomePageを見てみたんですが、
    COMってやっぱり結構厄介みたいですね。
    HW的な要因なんかもあるみたいです。

    一応、見つけたHPを紹介しておきます。

    Win32API シリアル受信プログラムデバッグの一例
     

    解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。
    • 回答としてマーク 鏑木肆星 2010年2月26日 8:07
    2010年2月22日 9:19
  • 大昔、フロー制御のできない機器(やれやれ)を相手にしたことがありますが大変でした。
    ちなみにスレ主さんの通信条件ってどうなってるんでしょう。やや気になります。
    • 回答としてマーク 鏑木肆星 2010年2月26日 8:07
    2010年2月23日 7:23
  • 皆さま

    たくさんのご回答ありがとうございました。
    とりあえず、現状と致しましては送信するデータ数を減らすことでCE_OVERRUN
    の対処をすることとしました。

    もう1点、分からない箇所がありまして、別のスレッドにて質問させていただきますので
    もしよろしければそちらも見て頂けたらなと思っております。
    よろしくお願い致します。
    2010年2月26日 8:07