Delphi TMessageManager


TMessageManager使ってますか?

この記事は、Delphi Advent Calendar 2018 の18日向けの記事です。

DelphiのFMXというライブラリにはTMessageManagerという、メッセージを投げ合う便利な機能が有ります。
必要なメッセージの購読を登録すると、そのメッセージを誰かが発行した時に受け取れる機能です。

使い方

uses
  System.Messaging; // 追加

type
  /// メッセージの準備
  // 送受信したいメッセージのクラスを作ります。
  // System.Messaging内のTMessage<T>から派生させます。
  // 今回は単純にStringを受け渡したいと思います。

  TMessageString = TMessage<String>;

  TOwnsMessage = class(TComponent)
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure RecvMessage(const Sender: TObject; const M: TMessage);
    procedure SendMessage(const S: String);
  end;

constructor TOwnsMessage.Create(AOwner: TComponent);
begin
  /// メッセージ購読の準備
  // 
  // マネージャを呼び出す
  var manager := TMessageManager.DefaultManager;

  // 購読したいクラスと、メッセージが飛んできた時に呼び出してもらいたい
  // 手続きを指定します.(今回はRecvMessage)
  // この時、IDがSubscribeToMessageから帰ってきますが、私は使いませんでした.
  manager.SubscribeToMessage( TMessageString, RecvMessage );
end;

destructor TOwnsMessage.Destroy;
begin
  /// メッセージ購読の解除
  // 
  // マネージャを呼び出す
  var manager := TMessageManager.DefaultManager;

  // 購読解除したいクラスと、購読時に指定した手続きを指定します.
  manager.UnsubscribeToMessage( TMessageString, RecvMessage );
end;

procedure TOwnsMessage.RecvMessage(const Sender: TObject; const M: TMessage);
begin
  // メッセージが流れてきた時に呼び出されるので M を所望のクラスにキャストして使用します。
  ShowMessage( TMessageString( M ).Value );
end;

procedure TOwnsMessage.SendMessage(const S: String);
begin
  // メッセージを送信します。
  // マネージャを呼び出す.
  var manager := TMessageManager.DefaultManager;

  // メッセージ送信
  manager.SendMessage( Self, TMessageString.Create( S ) );
end;

と、今回は同じクラスの中に押し込んだので便利さが伝わらないかもしれませんが、
同じメッセージクラスを違うクラス同士で投げ合えますので、何気に使い道が有るかと思います。

私が落ちた罠??????....(TT

TCPで通信するクラスがあって、その内容を上記TMessageManagerを使用してクラス間でやり取りしていました。
TCPの通信のクラスは、Threadで拵えていました。

TCPで受信した文字列をTMessageManager.SendMessageで他のクラスに投げていたのです。

(TTcpClientClass)TCPで受信 -> SendMessage -> (TMainForm)内容を解析して使用

不具合なく動いていたようなので、そのまま実稼働に移行しました。

しかし!!!!!!!!
現場で数時間動かすと、アプリが落ちたり応答無しになったり、、、(TT;

感の良い方はすでにお気づきだと思いますが、、、、

そうです。違うThread間でメッセージを何も考えずに投げ合っていたのです。
直接、GUIをいじったりしていたわけでは無いので、拵えていた時には気がつかなかったのです。。。

で、どうした?

  // 別スレッドからメインスレッドへ投げる時はSyncronizeを忘れずに..
  Synchronize( procedure
               begin
                 manager.SendMessage( Self, TMessageString.Create( s ) );
               end );

TMessageManagerはメッセージを投げるのには簡単ですが、
スレッド間の競合とかは、当然考えられていません。

ps

これは罠では無いだろうが!!と、お怒りの皆様。
 その通りでございます。m(~)m

ps2

こんなおいらの仕事を見て不安になって、手伝ってやっても良いぞ!!
という方がいらっしゃいましたら、ご一報よろしくお願いいたします。

都市土木関連ですので、現場に行ける方大募集〜〜(^^;