ワーカースレッドがメッセージを受信するためには?

解決


ゆうこ  2005-08-04 01:52:27  No: 16816

https://www.petitmonte.com/bbs/answers?question_id=2895
を拝見させていただき、ワーカースレッドがメッセージの受信が出来ないことを理解しましたが、
http://www.para.tutics.tut.ac.jp/~ohno/mu/devroom/delphi_qa_g.htmlには「ウィンドウハンドルを持たないコンポーネントにメッセージを受信させたい」方法が記載されています
その方法とは、AllocateHWndを使いメッセージとハンドラ関数を結びつけるものです。メッセージポンプが要りません。(わたしはそう認識しました)

ワーカースレッドがメッセージ受信が出来ないのは、メッセージポンプがないからという理由だけと思い、上記方法でならウィンドウハンドルを持たないワーカースレッドにメッセージを受信させることが出来るのではないかと考えました。

そこでTThreadの派生クラスを作成し、上記方法を試してみたところ、コンストラクタCreateを宣言している箇所で「静的メソッドはオーバーライドできません」とエラーが出てしまいます。

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
type
  ThreadTest = class(TThread)
  private
    FHandle:HWND;
  public
    property Handle:HWND read FHandle;
    constructor Create(AOwner:TComponent); override;
    destructor Destroy; override;
  protected
    procedure Execute; override;
    procedure WndProc(var Msg:TMessage);
  end;
↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

TThreadクラスのコンストラクタCreateはオーバーライドできませんか?
そもそも作成したクラス(TThreadTest)がコンポーネントではないのでこの方法は使えないのでしょうか?
独自の解釈で、見当違いのことを言っているかもしれませんがよろしくお願いします。


anone  2005-08-04 02:15:40  No: 16817

AllocateHWnd はメッセージを付け取るダミーのウィンドウを作るだけです。
ワーカスレッドではやはりメッセージは受け取れません。


ゆうこ  2005-08-04 02:40:22  No: 16818

anoneさん、ありがとうございます。

ダミーウィンドウを作る=メッセージポンプが出来る
ということではありませんか?
スレッドの実行関数(Execute)とメッセージ受信時のハンドラ関数(WndProc)が二つある時点で何かおかしい気はしています。
スレッド関数Execute処理中にメッセージ受信をしてWndProcが実行されるとは考えにくいですし。
出来るとすると、スレッド関数Executeがメッセージポンプを監視していて、
メッセージ受信時に該当する関数をコールするくらいでしょうか。
※Executeがディスパッチ的な役割???


anone  2005-08-04 03:55:00  No: 16819

> ダミーウィンドウを作る=メッセージポンプが出来る

そんなことはありません。メッセージキューはウィンドウズが作ってくれますが
メッセージループ(ポンプ)部分はユーザがコードで作らない限りメッセージは
受け取れません。ワーカスレッドでメッセージループを作ると、せっかくのワーカ
スレッドとしての役割が滞ります。ですから、ワーカスレッドでメッセージを
受け取るように努力するのは、有意義とは思いません。


ゆうこ  2005-08-04 04:08:40  No: 16820

anoneさん、ありがとうございます。

>ワーカスレッドでメッセージループを作ると、せっかくのワーカ
>スレッドとしての役割が滞ります。ですから、ワーカスレッドでメッセージを
>受け取るように努力するのは、有意義とは思いません。

例えば非常に短い間隔でハードウェアをポーリングを行い
状態が前回と違った場合のみ何らかの処理を行う場合、
メインスレッド(画面)が非常に短い間隔でタイマを設定すると、
画面がたびたび固まったように見える(描画が追いつかない?)のでは
ないかと危惧しています。
タイムアウトする度にハンドラ内でハードウェアの状態を比較しますが、そのほとんどが前回と値が一緒で比較処理以外何もしません。
その処理を別スレッド(ここで言うワーカースレッド)に託し、
変化が起こったときのみメインスレッド(画面)に通知する方法を取れないものかと考えていました。


メラトニン  2005-08-04 04:46:07  No: 16821

横から失礼します。
ワーカスレッドからのメッセージ送信は簡単にできますよ、
できないのはメッセージ受信です。
上の文章だとメッセージ受信は必要ないような気がしますけど、
それにWM_で返ってくる何かがあるならそれは普通にメインスレッドで受け取れば良いのではないでしょうか?(別に待ち処理は必要ないわけですし)
応答待ちがある処理(PINGを打つ等)の場合はWM_では返ってこないですよね…

勘違いしてたらご指摘ください。


ゆうこ  2005-08-04 05:16:33  No: 16822

メラトニンさん、ありがとうございます。

>上の文章だとメッセージ受信は必要ないような気がしますけど、
>それにWM_で返ってくる何かがあるならそれは普通にメインスレッドで受け>取れば良いのではないでしょうか?(別に待ち処理は必要ないわけですし)

ご指摘の通り、メインのスレッドで受け取って処理を行なえばよいかもしれません。
しかし、連続したタイマのタイムアウトメッセージを受け取る場合、
タイマのタイムアウト間隔をとことん短くすると、メインスレッド(画面)が
忙しくなってしまい、画面描画が追いつかなくなり、しまいには画面が固まった
ようになってしまうのではないかと危惧しています。
そのため、メインスレッド(画面)ではなく、別のスレッドがタイムアウトの
メッセージを受け取りたいと思った次第です。


anone  2005-08-04 06:14:14  No: 16823

どのようにお考えがあろうとも、メッセージはスレッド自体がメッセージポンプを
実装しなければ受け取れません。


anone  2005-08-04 06:21:00  No: 16824

> タイマのタイムアウト間隔をとことん短くすると、メインスレッド(画面)が
> 忙しくなってしまい、画面描画が追いつかなくなり、しまいには画面が固まった
> ようになってしまうのではないかと危惧しています。

タイマの役割は、決まった時間間隔で WM_TIMER をキューに入れるだけです。
一回の OnTimer イベント処理中に溜まった WM_TIMER はまとめられて一つに
なります。少なくても、OnTimer イベントの処理が終えるたびにメッセージ処理の
機会が訪れます。ですから、設定時間が短いと、かえって固まることは少ないでしょう。


メラトニン  2005-08-04 07:11:54  No: 16825

とにかく多くのメッセージが来るということで
取りこぼしありでよければアプリケーションが受け取ったメッセージをThreadに横流しするという手段でPostThreadMessageはどうでしょうか?
といっても私も使い方が分からなかったので引用です。
この方法だとアプリケーションがぎこちなくなる事は無いみたいです。

//http://www.elists.org/pipermail/delphi/1999-September/002021.html
//を引用
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TMeThread = class(TThread)
  private
    { Private 宣言 }
    procedure WMUser(var Msg: TWMNoParams); message WM_USER;
  protected
    procedure Execute; override;
  end;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    MeThread1 :TThread;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TMeThread }

procedure TMeThread.Execute;
var
  Msg: TMsg;
  DMsg: TMessage;
begin
  PeekMessage(Msg,0,0,0,PM_NOREMOVE); // Create Message Queue
  while not Terminated do begin
     Sleep(10); // don't eat CPU cycles
     if PeekMessage(Msg,0,0,0,PM_REMOVE) then begin
       DMsg.Msg:=Msg.message;
       DMsg.wParam:=Msg.wParam;
       DMsg.lParam:=Msg.lParam;
       DMsg.Result:=0;
       Dispatch(DMsg);
     end;
  end;
end;

procedure TMeThread.WMUser(var Msg: TWMNoParams);
begin
   Beep;
end;

//Form1
procedure TForm1.Button1Click(Sender: TObject);
var i:integer;
begin
  for i:=0 to 100 do //負荷を掛けてみるテスト
  PostThreadMessage(MeThread1.ThreadID,WM_USER,0,0);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  caption:='作成しました';
  MeThread1:=TMeThread.Create(False);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  MeThread1.Terminate;
  MeThread1.WaitFor;
end;

end.


ゆうこ  2005-08-04 18:28:09  No: 16826

anoneさん、ありがとうございます。

>どのようにお考えがあろうとも、メッセージはスレッド自体がメッセージポンプを
>実装しなければ受け取れません。

「ワーカスレッドはメッセージを受け取れない」ではなく
「ワーカスレッドがメッセージを受け取るにはプログラマが実装する必要がある」
ということでしょうか?
結論として「ワーカスレッドでメッセージを受け取ろうと思えば受け取れるが、
その意義が問われる。」といった感じでしょうか?

>一回の OnTimer イベント処理中に溜まった WM_TIMER はまとめられて一つに
>なります。

OnTimerイベント処理中にWM_TIMERメッセージが複数メッセージキューに
溜まることが無いということでしょうか?
これはWM_TIMERだけに限った事でしょうか?(他のメッセージはしませんよね?)
※WM_TIMERをメッセージキューにポストする側が、そのメッセージキューに
WM_TIMERが存在した場合、メッセージをポストしない??

>少なくても、OnTimer イベントの処理が終えるたびにメッセージ処理の
>機会が訪れます。ですから、設定時間が短いと、かえって固まることは
>少ないでしょう。

メインスレッド(画面)がなんらかのメッセージ処理を行なっている間は、
画面描画が出来ないのではないでしょうか?
メッセージキューにメッセージが無く、メインスレッドが待ち状態(と言う
のかわかりませんが)になっている最中に画面描画が行われると認識しています。
それとも画面描画指示のメッセージも同じメッセージキューにポストされる
ということでしょうか?

メラトニンさん、ありがとうございます。

>とにかく多くのメッセージが来るということで
>取りこぼしありでよければアプリケーションが受け取ったメッセージを
>Threadに横流しするという手段でPostThreadMessageはどうでしょうか?

ソースまで提示してくださり、本当にありがとうございます。
取りこぼしはありでぜんぜん良いです。
メッセージ処理関数自体が処理を行う時間が大きい場合、提示して頂いた方法は
とても有効と思います。
今回、メッセージ処理関数の処理時間というよりも、
「メッセージを受信」→「ディスパッチ処理」のオーバーヘッドを
考慮しています。
メインスレッドでメッセージ受信後ほかのスレッドへ横流しをする場合、前者は
対処できても、後者の問題は残るのではないかと思うのです。


anone  2005-08-04 19:49:37  No: 16827

> 結論として「ワーカスレッドでメッセージを受け取ろうと思えば受け取れるが、
> その意義が問われる。」といった感じでしょうか?

そうです。

> OnTimerイベント処理中にWM_TIMERメッセージが複数メッセージキューに
> 溜まることが無いということでしょうか?

そうです。OS がひとつにまとめてくれます。WM_PAINT も更新領域の or を
とってひとつにまとめられます。

> メッセージキューにメッセージが無く、メインスレッドが待ち状態(と言う
> のかわかりませんが)になっている最中に画面描画が行われると認識しています。

違います。GUI の表示の更新も WM_PAINT などのメッセージを処理してウィンドウ
関数のコードまたはデフォルトウィンドウ関数が実行されて行われます。画面更新
もメッセージ処理なのです。メッセージによっては、キューに並ぶ順番の優先順位
があります。描画更新用のメッセージは優先順位が低いので、たしかにアイドル直前の
最後に実行されるような感じではありますね。

ワーカスレッドをイベントドリブンにすると、結局、順番待ちの処理がひとつ増える
だけで、マルチスレッドの意義が台無しのような気がします。メインスレッドが
大忙しなら、マルチスレッドにしても速度の向上は望めないでしょう。マルチ
スレッドにするのは画面が凍りつかないようにするためですからメッセージ
処理はメインスレッドが専念すべきです。

まず、懸念される前に何でもやってみてはいかがでしょうか。


ゆうこ  2005-08-04 20:41:22  No: 16828

anoneさん、ありがとうございます。

疑問がおおよそクリアになった感があります。
ありがとうございます。

>まず、懸念される前に何でもやってみてはいかがでしょうか。

仰る通りかと思います。申し訳ありません。
只、画面描画の問題はやってみて大丈夫だったらOKとはならないと思うのです。
自分のPCのスペックが良く(又は悪く)て、たまたまその現象が起きたり
起きなかったりすると思い、根本をじっくり考えてみようと思った次第です。
不快な思いをされているのでしたら、申し訳ありません。

>マルチスレッドにするのは画面が凍りつかないようにするためですからメッセージ
>処理はメインスレッドが専念すべきです。

「メッセージ処理はメインスレッドが専念すべき」という結論が
私には導きだせませんでした。
私は「メインスレッドは画面描画メッセージ処理に専念出来る状態にすべき」と思うので
画面描画以外のメッセージ処理は他スレッドが行なったほうが良いと結論付けて
しまいそうです。

又、
>設定時間が短いと、かえって固まることは少ないでしょう。
について考察してみたのですが、やはり理解できませんでした。

TM          TM          TM
↓          ↓          ↓
---------------------------------→
A   B     A   B     A   B

TM:タイムアウト
A:タイムアウトメッセージ受信(WM_TIMER)
B:タイマ処理関数完了
A→Bの間がタイマ処理関数の実行時間であり、
他の処理は何も出来ません。
B→Aの間が、他の処理をする時間と認識しています。
画面描画処理もB→Aの間に行うものと思っているのですが、
タイムアウト間隔を短くすると、B→Aの時間が短くなり
やはり、画面描画処理が出来づらくなるのではないかと思うのです。
※私のPCでは、ある一定の間隔まではタイマを短くしていったほうが
  画面が固まりやすいように感じます。
  ある間隔を境にそれ以上悪化することがありませんでした。
  恐らく、メッセージのまとめ処理が行われているのだろうと思いました。


ゆうこ  2005-08-04 20:44:41  No: 16829

ワーカースレッドがメッセージを受信するためには?
に関しては

結論→「メッセージポンプをプログラマが作成すれば可能である」

で解決とさせて頂きます。
皆様のご助言本当にありがとうございました。


メラトニン  2005-08-04 23:31:24  No: 16830

ちょっと作成しようとしているアプリケーションが見えないんですが、
1.メインスレッドでタイマー関数ってる点が疑問です。
2.「ディスパッチ処理」をメインスレッドでする必要も無い気がします。これら二つの動作はスレッド内に持ち込めない理由があるのでしょうか?
それと別案ですが、もう大部分が固定されてしまって出来上がってるアプリケーションであれば、内部処理とは別にもう一個表示専用のアプリケーションを作ってしまうという手もいかがなもんでしょうか?


ゆうこ  2005-08-05 00:03:13  No: 16831

メラトニンさん、ありがとうございます。

>1.メインスレッドでタイマー関数ってる点が疑問です。

すみません。ちょっと理解できないのですが、「タイマー関数を
メインスレッドで実行している点が疑問」と仰っておりますでしょうか?

>2.「ディスパッチ処理」をメインスレッドでする必要も無い気が
>します。

すみません。こちらもよく理解できておりません。
「ディスパッチ処理をメインスレッドで行う必要がない」
ですが、OSが行うディスパッチ処理以外にメインスレッドで明示的に
ディスパッチ処理を行うことは考えておりません。
そういうことではないでしょうか?

>これら二つの動作はスレッド内に持ち込めない理由があるのでしょうか?
これら二つの動作(タイマ関数処理とディスパッチ処理)をメインとは別の
スレッドで実行させたいと考えていました。
わかりづらくてすみません。

>ちょっと作成しようとしているアプリケーションが見えないんですが、

大変厚かましくも、簡単に説明させていただきますと、
PCには外付けのIOボードが装着されています。
そのIOボードに接続されているボタンを押すと、それをトリガとして別のポートに接続されているプリンタに出力させます。
画面には、プリンタ出力させた枚数等の情報を表示させようと考えています。
IOボードに割り込みは無く、ポーリングする必要があります。
ポーリングのタイミングをタイマで行うことを考えておりました。


anone  2005-08-05 01:31:03  No: 16832

> ポーリングのタイミングをタイマで行うことを考えておりました。

これこそワーカスレッドの役割なのでは?
Sleep() を含むループで監視して、信号があったらメインスレッドに
メッセージを送ればいいと思います。


ゆうこ  2005-08-05 02:56:49  No: 16833

anoneさま、ありがとうございます。

>> ポーリングのタイミングをタイマで行うことを考えておりました。
>これこそワーカスレッドの役割なのでは?
>Sleep() を含むループで監視して、信号があったらメインスレッドに
>メッセージを送ればいいと思います。

はい。現在その方向で進めております。
この方法は前から検討していたのですが、
100ms毎のポーリングをしようとした場合、

while true do
begin
  sleep(100);  //100msスリープ

  //処理
end;

と記述しますが、
・100ms毎と言うものの実際は100ms + 処理時間毎のポーリングになってしまう。
・スレッド終了のタイミングが難しい。→sleepした瞬間に終了したくなった場合
  sleepから抜けるまで終了が出来ない。
  (別スレッドから強制的に終了させることをしないと考えると)

そのため敬遠しておりましたが、

・ポーリング間隔はそんなに正確な精度を必要としない。
・sleepする時間を短くし、スレッド終了をなるべく随時行なえるようにする。

で対処しようと考えました。


メラトニン  2005-08-05 07:46:33  No: 16834

既に解決気味のようで何よりです。
感性プログラマなもので舌足らずな所はご指摘ください。

>1.メインスレッドでタイマー関数ってる点が疑問です。
もうこれは解決っぽいですが、ワーカスレッドがタイマーの代用というか今回の目的とされている動作(メインスレッドが止まらない)に適しているので、タイマーで行う処理はワーカスレッドで行うべきと言う事です。

>2.「ディスパッチ処理」をメインスレッドでする必要も無い気が
>します。
OSが行うディスパッチ処理はメインスレッドを停滞させませんので当然ながら明示的になにやらやっているものと考えていました。

つまりこれら(2は勘違い)の事をワーカスレッド内で行えばどうでしょうという事でした。
ちなみにワーカスレッドは優先度を低くすればsleepなんぞ無くても構いません。
それに、OSの許す範囲内で複数の種類を何個でも作成できるので、メインスレッドはその同期にのみ徹してしまえばよいのです。


anone  2005-08-05 14:25:25  No: 16835

> ちなみにワーカスレッドは優先度を低くすればsleepなんぞ無くても構いません。

いや、プログラマがタスクスイッチをある程度制御できるという意味では、絶対、優先順位
を低くするより、通常順位で sleep() 使った方がいいです。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加