RS-232通信プログラムを作成しています。
環境は、OS:XP(SP2)、VC++6.0で、MFCのダイアログベースのアプリです。
受信データをリアルタイムにグラフに描画するようにしています。しかし、グラフのス
クロールをしたり、ウィンドウの移動や最大化・最小化などを行うと、通信が一時的に
止まってしまいます。プログラムでは、1秒間毎にデータ要求コマンドを送信していま
す。GUIでの負荷が重くなると、この送信コマンドが正確に1秒間毎に送られていないよ
うです。ちなみにタイマーはSetTimer(ID、1000、NULL)を使用しています。
どのように解決してよいか困っています。
アドバイスをよろしくお願いします。
おそらく送信側に問題はないでしょう。むしろ受信して描画する方の
質が疑われます。
一般に、通信の受信部分は別にスレッドを作成してその中で行いますが、
その様になっていますか?
受信部分ですが、別スレッドを立てています。
イベント駆動として、受信があった場合にのみ、メインスレッドにメッセージをポスト
しています。受信側に問題があるようには思えないんですが、まだはっきりとはわかり
ません。
ところで、SetTimerの動作はどのようになっているのでしょうか?
精度は別としても、必ず指定した時間で駆動するのでしょうか?それとも、負荷が重く
なった場合などは、その分ずれるのでしょうか?
SetTimerは指定時間後にWM_TIMERメッセージをアプリケーションキューにポストしますので、
アプリケーションが高くて、メッセージの取り出しが遅れればその分処理が遅れます。
# そういうことを聞いているのではない?
> アプリケーションが高くて、
アプリケーションがの負荷が高くて、
maruさん、ありがとうございます。
現在のアプリでは、ユーザーの操作によってウィンドウの再描画が行われるときに、通
信(送信)の遅延が発生しているようですので、SetTimerの遅れがそのまま、送信の遅
延になっていると思います。
SetTimerとウィンドウ描画を別スレッドで行うことはできるのでしょうか?
1. 送信部分もスレッドにする。
2. SetTimerで1秒たったらスレッドにイベントを送る。
3. イベント駆動で送信する。
ではどうでしょうか?
SetTimerは普通でも200mS位の誤差はあります。
それを覚悟して使わないとだめです。
あと、要求信号→データ受信の手順が変わらないのなら、
データ送信後に受信スレッドにイベントを送り受信開始という手も
ありですね。
ITOさん、ありがとうございます。
現在、メインスレッドにSetTimerと描画関数があります。
送信スレッドをメインスレッドと別に立てたとしても、描画関数がビジー状態になった
場合、SetTimerもメッセージをポストできなくなるので、送信スレッドに対するイベン
トも発生しないのではないでしょうか?
SetTimerの誤差については承知の上で使用しています。ただ、1秒に対して、100~200ms
程度の誤差が生じても仕方ないですが、ビジー状態になって、数秒間も遅れてしまうこ
とが問題となっています。
描画関数については、重くならないように書いているつもりですが、意地悪テストとし
て、ウィンドウを高速で移動し続けたり極端に重くなるようなことをすると、送信の遅
延が生じてしまいます。
それで、描画関数(OnPaint)とSetTimerを別のスレッドにしたいのですが、どのように
したらよいでしょうか?
ITO さんの案ではだめなんですか?
記憶違いでなければウィンドウに関する操作をスレッドで行うのは危険なので、
私は OnPaint などでの処理はそのままにし、それ以外の部分をスレッドに
外だしするような書き方をします。
で、スレッドのほうは、OnTimer (によるスレッドへのメッセージ送信など)待ちでは
>送信スレッドをメインスレッドと別に立てたとしても、描画関数がビジー状態になった
>場合、SetTimerもメッセージをポストできなくなるので、送信スレッドに対するイベン
ということになるので、以下のようなことをしたりします。
( ITO さんの 1 番の案の例 )
while( !終了条件 ) {
... // 通信処理など
if( 終了条件 ) break;
ulWaitTime = ( 1000 - ( ::TimeGetTime() % 1000 ) ); // 次周期までの時間を ms
単位で取得
::Sleep( ulWaitTime );
};
これでももちろん誤差はありますが。
別のことを質問されているのでしたらごめんなさい。
紅’さんありがとうございます。
SetTimerにこだわっていたというか、それ以外の方法が思い浮かばなかったので、ずっ
とSetTimerのことをお聞きしていましたが、紅’さんのように、送信スレッドの中に、
SetTimerとは異なる時間処理ルーチンを組み込めば、メインスレッドとは無関係に機能
するはずですよね。ご指摘ありがとうございます。
>送信スレッドをメインスレッドと別に立てたとしても、描画関数がビジー状態になった
>場合、SetTimerもメッセージをポストできなくなるので、送信スレッドに対するイベン
僕が言っているのは、「OnTimer」ルーチンからのイベント送信ですけど間違いないです
ね?
ご存知でしたら読み飛ばしてください。
「SendMessage」,「PostMessage」等ではないです。
「CEvent」、「CleateEvent,SetEvent」等です。
スレッドは「WaitForSingleObject」で待ちます。
この方法で処理すればいいはずですが,どうでしょうか?
タイマ識別子を別にしたらどうですか?
> この方法で処理すればいいはずですが,どうでしょうか?
BORRELさんの場合は描画に忙しくてメッセージを受けられない(メインのメッセージ
ループが待たされている)ようなので、OnTimerの呼び出しが待たされているんだと
思います。
タイマ処理をコールバック関数で処理するようにしたらどうだったかな?
MSDNには
「lpTimerFunc
uElapse パラメータで指定したタイムアウト時間が経過した時点で呼び出される、1 個の関数
へのポインタを指定します。このコールバック関数の詳細については、TimerProc 関数の説明
を参照してください。」
と書いてあるけど、試していない。
「TimerProc コールバック関数を指定すると、既定のウィンドウプロシージャは WM_TIMER メ
ッセージを処理する際にそのコールバック関数を呼び出します。したがって、WM_TIMER メッセ
ージを処理する代わりに TimerProc 関数を使う場合も、呼び出し側スレッド内でメッセージを
ディスパッチ(転送)する必要があります。」
とも書いてあるから、やっぱり駄目かな?
ITOさんが考えておられることは以下のようなことでしょうか?
確認させてください。
----メインスレッド----
#include afxmt.h
CEvent g_eventStart;
void CMainThread::OnButton1()
{
...
AfxBeginthread(ThreadFunc,GetSafeHwnd() );
SetTimer(1,1000,NULL);
...
}
void CMainThread::OnTimer(UINT nIDEvent) //タイマー処理ルーチン
{
...
g_eventStart.SetEvent();
...
}
----送信スレッド----
UINT ThreadFunc(LPVOID pParam)
{
::WaitForSingleObject(g_eventStart,INFINITE);
...
}
この方法ですと、簡易的に同期をとることはできますが、OnTimerの処理能力に依存する
ことに変わりがないような気がするのですが、ITOさんの考えておられることは、上記の
内容とは違うものですか?
まだWindowsの動作に関して詳しくわかりませんので、もし違っていたらすいません。
> この方法ですと、簡易的に同期をとることはできますが、OnTimerの処理能力に
> 依存することに変わりがないような気がするのですが、
> ITOさんの考えておられることは、
> 上記の内容とは違うものですか?
同じです。
ただ、問題の描画のルーチンのタイマ識別子を別にしたらどうかなと思ったのですが
どうでしょうか?
maruさんが回答出してますね.....だめかもしれませんね。
>maruさんが回答出してますね.....だめかもしれませんね。
駄目と聞いたことがあります。
知ってる範囲でこういうことに使えそうなタイマの種類や方法色々。
・マルチメディアタイマ(timeSetEvent)
→優先度が高い。NT系だと平均呼び出し間隔が指定通りだが、9x系だと若干
少なくなる。DirectDrawと同時に使う事でリセットが掛かる環境があるらしい。
・ウィンドウタイマ(SetTimer、WM_TIMER方式とコールバック方式)
→指定したウィンドウのメッセージポンプで処理されるまで機能しない。
メッセージ優先度はWM_PAINTの次に低く下から二番目の優先度しかない。
・通常のタイマを使い、指定時間が来るまで待機するスレッドを作る。
・メッセージポンプの合間で処理をする方法
→処理の遅いメッセージがあるとタイマ処理間隔が悪くなる。
逆にインターバル処理が遅いとGUI応答間隔が悪くなる。
・完全に別スレッドを作る方法
→マルチスレッドゆえの考慮すべき事がある。
**通常のタイマの種類
・GetTickCount
→根本的にtimeGetTimeだが、保障されている精度が若干それより低い。
・timeGetTime
→良くあるマルチメディアタイマ
・QueryPerformanceCounter
→環境依存
・DirectMusicのマスタークロック
→超高精度だがDirectX必須
この辺り以外にも有りますが有名どころはこの辺りでしょうか。
別スレッドにSetTimerした場合でもメッセージキューが分離する筈なので、それ
でいける可能性は有ります。
が、精度が低いのは相変わらずなので他の方法も検討する価値はあると思いま
すが。