SetTimerをメインスレッドとは別に新しいスレッド上で実行する方法がありましたら教
えていただけないでしょうか。
環境はWindowsXP,VC++6.0のMFCダイアログベースアプリを作成しています。
単に新規スレッドを立てた場合(UINT NewThread(LPVOID pParam)関数を作成)、ウィン
ドウプロシージャがないため、WMメッセージを受け取ることはできないと思うのです
が、そのようにしたらメッセージを受け取ることができるのでしょうか?
よろしくお願いします。
純粋に設問に答えるだけなら「 UI スレッドを作成すればよい」となるわけだが
そもそも WM_TIMER (SetTimer) を使う必然があるかどうかを再考慮するべき。
UINT DataLoggingThread(void* p) {
while (ExitFlag==0) {
Sleep(1000);
GetDataFromDevice(); // データ取得関数を自作する
RequestRedraw(); // UI スレッドに画面更新要求
}
return 0;
}
この程度で事足りる、のではないか?
定期処理を別スレッドで行いたいのならマルチメディアタイマ(timeSetEvent)
を使うという手もありますが、tetrapodさんの仰る通りサブスレッド内で
while ( !終了条件) {
Sleep(xxx);
定期処理();
}
これで十分だと思いますね。
説明不足で申し訳ありません。
確かにSetTimerを使う必然性は全くありません。指摘していただくまでもなく、ループ
とSleep(…)で十分事足ります。これは、すでに質問する前から動作を確認しています。
ただしSleepだと、処理関数の処理時間によって徐々に時間がずれていきますが。
ただ、最善の方法ではないとしても、純粋にテクニックとしてWMメッセージを受け取る
ことができるのか、ということを知りたくて質問しました。
UIスレッドを使用すればよいということですが、ワーカスレッドしか作成したことがな
いので、この機会にUIスレッドを調査してみます。
特にスレッドとする必要無いかもしれません。
::SetTimer()の第一引数のHWNDをNULLにして、第4引数に
タイマーコールバック関数のポインタを指定すれば、
指定時間後その関数がコールバックされます。
この方法だとメッセージキューの渋滞の影響を受けにくくなります。
というか、こっちが伝統のタイマー割り込みの使い方であり
MFCの実装は、はっきり言えばかなりなげやり(笑)。
> ただしSleepだと、処理関数の処理時間によって徐々に時間がずれていきますが。
なぜそう思う?頭固いのではないかな?
// データ要求を指示するだけのスレッド
UINT KickingThread(void* p) {
while (ExitFlag==0) {
Sleep(1000);
KickGetData(); // データ取得専用スレッドに取得開始を要求
}
return 0;
}
// データ取得スレッド
UINT GetDataThread(void* p) { ... }
KickGetData() の実行時間が実質0であれば、ずれていかないと思うが。
スレッド2つも3つも実装上のめんどくささは、そう大差ないと思う
例えば、こんな手法もありますね。
1. SetTimer()でタイマを設定
2. OnTimer()ハンドラで、別スレッド対しにPostThreadMessage(..WM_TIMER..)
3. 別スレッドでは以下のようにメッセージを待機
while (::GetMessage(&msg, NULL, WM_QUIT, WM_TIMER)) {
// 定期処理
}
> ただしSleepだと、処理関数の処理時間によって徐々に時間がずれていきますが。
1ミリのずれも許されない処理なら、やはりマルチメディアタイマかと^^;
ひょっとして。
質問の趣旨は、周期処理がどうとかということではなくて、
ウィンドウメッセージをメインスレッド以外が横取りすることは可能か、
ということなのでしょうか。そのメッセージの代表が WM_TIMER で。
であれば、かつ、可能なら、私も知りたいです。
既存プログラムの改修などの都合で OnTimer にこだわらなければ
ならない理由があるのでしたら、それを説明しないともう進展はないと
思いますが。
> ただしSleepだと、処理関数の処理時間によって徐々に時間がずれていきますが。
SleepとSetTimerは、指定時間待つか、指定時間経過後「WN_TIMER」
を送るかの以外だけだと認識してます。
Sleepがずれるのなら、SetTimerも「WN_TIMER」送る都度ずれていくはずだと思います。
今のところそのような傾向は確認してないのでまずないのではと思います。
> ::SetTimer()の第一引数のHWNDをNULLにして、第4引数に
> タイマーコールバック関数のポインタを指定すれば、
> 指定時間後その関数がコールバックされます。
これは、やってみる価値があるではないでしょうか。
> 質問の趣旨は、周期処理がどうとかということではなくて、
> ウィンドウメッセージをメインスレッド以外が横取りすることは可能か、
横取りは難しいのではないでしょうか。
する方法はあると思います。
やったことがないので推測ですが、
tetrapod さんの最初の案の通り「UIスレッド」を作ってHWNDを作ることが
できればればメッセージを転送してもらうことは可能かなとはおもいますが
どうでしょうか。
質問の意図は、技術的な面での質問です。
違う方法でうまくいくとか、この方法で十分ということはわかっているうえでの質問で
す。SetTimerはあくまで例として出しました。
皆さんからの回答からワーカスレッドでは、ポストされたメッセージを受け取れないの
で、スレッドメッセージを受け取れるようにするにはUIスレッドにするということで、
認識していますがあっているでしょうか?
話がまた元に戻ってSleep関数ですが、実際詳しくわかっているわけではないですが、
while (ExitFlag==0) {
Sleep(1000);
KickGetData(); // データ取得専用スレッドに取得開始を要求
}
という場合、仮にKickGetData()が処理に100msかかる場合、処理と処理の間の時間は
1100msにならないんでしょうか?あくまで処理時間を100msと仮定した場合ですが。
これは、SetTimerを使用した場合でも同じでしょうか?
ものすごい高い精度を希望しているわけではないですが、処理関数も多少時間のかかる
処理をしていますので、理想的には、割り込み処理のような処理ができればいいと思っ
ているのですが、難しいでしょうか?
たとえば、呼び出し時間間隔の仕様を1000ms±200msとした場合に、KickGetData()があ
る時は、110ms要し、ある時は300ms要するような特殊な場合は、仕様を満たすことがで
きないのでしょうか。最初に書いたように技術的に可能かどうかの質問ですから、実際
そんな時間がかかる処理なのかといったことは無視しての質問です。
動作OSがNT系でカーネル割り込みの処理負荷を考慮できるならばWaitableTimer
というのも手カナ、と思ったり。
WaitableTimerであれば通常のタイマーほどズレが発生しないかと思われます。
# マルチメディアタイマーが挙がっているので、そちらの方がWaitableTimerより
# いいのカナ。どちらが望ましいかはわかりません。
> 皆さんからの回答からワーカスレッドでは、ポストされたメッセージを受け取れない
::PostThreadMessage()では駄目なのですか?
> 皆さんからの回答からワーカスレッドでは、ポストされたメッセージを受け取れない
> ので、スレッドメッセージを受け取れるようにするにはUIスレッドにするということ
> で、認識していますがあっているでしょうか?
少なくとも、SetTimer に関してはYes。
既に出ている通り、SetTimerにウィンドウハンドルではなくコールバック関数を指定すれ
ばワーカースレッドでも通知を受け取れるが、あくまでメッセージで受け取りたいという
ならUIスレッドにしなければならない。
ただし、WM_TIMER以外のメッセージ一般に関してならNo。
ウィンドウを持たないが、メッセージループを持つスレッドを作ることはできる(そうい
うスレッドをワーカースレッドと呼ぶのかUIスレッドと呼ぶのかは知らないが)。
スレッドに対して直接メッセージをポストするには、PostThreadMessage関数を使う(ま
たは、PostMessageの第一引数をNULLにする)。
> 仮にKickGetData()が処理に100msかかる場合、処理と処理の間の時間は
> 1100msにならないんでしょうか?
> SetTimerを使用した場合でも同じでしょうか?
1100ms より短くなることは無いでしょう。多少長くなることはあっても。
> たとえば、呼び出し時間間隔の仕様を1000ms±200msとした場合に、KickGetData()が
> ある時は、110ms要し、ある時は300ms要するような特殊な場合は、仕様を満たすことが
> できないのでしょうか。
KickGetDataに、実際何msかかったかを調べて、その分だけSleepの時間を増減させてやる
とか…
思いつきで言ってるので、うまくいくかどうかは知りません。計算コストのほうが多かっ
たりして。
なんか難しく考えすぎてると思うが・・・
俺の 2008/01/29(火) 10:40:50 の意図は
void KickGetData() {
::SetEvent(event_awake_aquire_thread);
}
位の実装にしなはれ、ということ。この程度なら必要時間は実質0だろう
実際のデータの取得に 100msec も 300msec もかかるとしたら、
それは MFC の UI スレッドでもなく、間隔を計測するスレッドでもなく
データ取得専用の3つ目のスレッドにしなはれ。
データ取得専用スレッドは通常は寝ていて、起きるのは取得実施のときだけ
WaitForSingleEvent(event_awake_aquire_thread); で待ってればいい。
> という場合、仮にKickGetData()が処理に100msかかる場合、
俺のコメントは、それだけの時間は絶対にかからない場合を想定してる。
と、書いたつもりだったが読み取れなかっただろうか?
データ取得に 1000msec 以上がかかる場合とか
バグなどによって要求が2つ以上同時に発生する場合とか
そーいうデバッグはもちろん必須
> while (ExitFlag==0) {
> Sleep(1000);
> KickGetData(); // データ取得専用スレッドに取得開始を要求
> }
は、「1秒待って、処理をして」の繰り返しだから、KickGetData()の処理時
間分づつ遅れるのは、自明でしょ。
それを避けたければ、
a. KickGetData()の時間を最小にする。
(tetrapodさんが提示)
b. 処理に掛かった時間分sleepの時間を調整する。
(紅' さんが昨日提示、シャノンさんも言及)
c. その他の方法を考える。
位かな。
c. その他の方法の一つに「メッセージループを持つスレッド(=UIスレッド
?)を作成するって言うのがあるんだと思う。
SetTimerは時間をセットしてから(SetTimerを呼び出してから)メッセージ
をキューにポストするまでの時間だから、ほぼ一定間隔で処理できるんじゃ
ないかな。メッセージが到着後すぐ、またはコールバック関数の先頭で、タ
イマを再設定するという前提だけど。
私としてはWindows OSにリアルタイム性を求めるってのが、無理なんじゃな
いか、と思っています。同じプログラムでも、マシンスペックが違えば処理
時間もちがうし。