Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Too many Threads Solution #2

Open
TheOriginalBytePlayer opened this issue May 25, 2022 · 2 comments
Open

Too many Threads Solution #2

TheOriginalBytePlayer opened this issue May 25, 2022 · 2 comments

Comments

@TheOriginalBytePlayer
Copy link

TheOriginalBytePlayer commented May 25, 2022

I was using your great component only to discover that it was creating a huge number of threads (200+) in our application which made debugging it basically impossible under Win64. I put together a simple -- read perhaps kludged solution -- that brings it down to 2 threads which I'm sure you could make more elegant and generic, if you desired.

Basically the way I did it was to create a centralized collector of components that want to receive messages, one which receives all the messages sent using an internal messaging channel, then feeds them back out to the registered components through an exterior messaging channel. This brings it down from one thread per IMessagingDispatcher to only two threads regardless as to how many components are registered.

I did some timing on it and 1600 calls took 6ms so I didn't bother trying to do any additional optimization though I'm sure it could be done.

There are two parts to this, the first is a component you can just drop on a form and it will automatically register the form with the messaging system.

The second is a SendMessage function that you can use with a generic messaging structure to pass most data.

unit VSoft.Messaging.Component.kjs;

interface

uses
  System.SysUtils, System.Classes, FMX.Forms, FMX.Types, FMX.Controls,
  System.Messaging,  VSoft.Messaging;

type

  TMessagingInitializer = class(TComponent)
  private
    { Private declarations }
    fEnabled:Boolean;
    procedure InitializeDispatcher;
  protected
    { Protected declarations }
  public
    { Public declarations }
    constructor Create(AOwner:TComponent); override;
    procedure SetEnabled(const Value:Boolean);
  published
    { Published declarations }
    property Enabled:Boolean read fENabled write SetEnabled;
  end;

   TGeneralPurposeMsg = record
      MsgID  : TMessageID;
      Filler : TMessageFiller;
      BinaryData:Pointer;
      StringData:String;
      ObjectData:TObject;
   end;


procedure Register;

procedure RegisterForMessageDispatcher(ForComponent:TComponent);
procedure UnRegisterForMessageDispatcher(ForComponent:TComponent);
procedure SendMessage(InMessageID:TMessageID;inStringData:String;
   InBinaryData:Pointer;InObjectData:TObject;SendDirectly:Boolean);

implementation
uses System.Contnrs, System.Diagnostics,  FMX.Dialogs;

type

   TInternalPurposeMsg= record
      MsgID  : TMessageID;
      Filler : TMessageFiller;
      PublicMsgID:TMessageID;
      BinaryData:Pointer;
      StringData:String;
      ObjectData:TObject;
   end;

const
   WM_USER             = $0400; //declaring here so we don't have to reference winapi.messages
   MSG_GENERAL_PURPOSE = WM_USER + $2000;

type
  TPooledMesageDispatcher = class(TComponent)
   strict private
     InternalMessageDispatcher:IMessageDispatcher;
     ComponentReceiverList:TObjectList;
   public
     ExternalMessageDispatcher:IMessageDispatcher;
     Procedure InternalMessage(var Msg:TInternalPurposeMsg); Message MSG_GENERAL_PURPOSE ;
     constructor Create(AOwner: TComponent); override;

     destructor Destroy; override;
     procedure Notification(AComponent: TComponent;  Operation: TOperation); override;
     procedure AddNotificationReceiver(InComponent:TComponent);
     procedure RemoveNotificationReceiver(InComponent:TComponent);
  end;


var
  ExternalMessageChannel:IMessageChannel;
  InternalMessageChannel:IMessageChannel;
  PooledMessageDispatcher:TPooledMesageDispatcher;

procedure SendMessage(InMessageID:TMessageID;inStringData:String;
   InBinaryData:Pointer;InObjectData:TObject;SendDirectly:Boolean);
begin
    var MsgToSend:TInternalPurposeMsg;
    MsgToSend.MsgID:=MSG_GENERAL_PURPOSE;
    MsgToSend.PublicMsgID:=inMessageID;
    MsgToSend.StringData:=InStringData;
    MsgToSend.BinaryData:=inBinaryData;
    MsgToSend.ObjectData:=inObjectData;
    if SendDirectly then
       PooledMessageDispatcher.InternalMessage(MsgToSend)
    else
       InternalMessageChannel.Queue.SendMessage(MsgToSend); //sync
end;

procedure RegisterForMessageDispatcher(ForComponent:TComponent);
begin
  PooledMessageDispatcher.AddNotificationReceiver(ForComponent);
end;

procedure UnRegisterForMessageDispatcher(ForComponent:TComponent);
begin
  PooledMessageDispatcher.RemoveNotificationReceiver(ForComponent);
end;


procedure Register;
begin
  RegisterComponents('FrameForge Custom Components', [TMessagingInitializer]);
end;

{ TMessagingInitializer }

procedure TMessagingInitializer.InitializeDispatcher;
begin
  var ParentForm:=Owner;
  while Assigned(ParentForm) and not (ParentForm is TCommonCustomForm) do
    ParentForm:=ParentForm.Owner;
  if assigned(ParentForm) and (ParentForm is TCommonCustomForm) then
   begin
      if Enabled then
         PooledMessageDispatcher.AddNotificationReceiver(ParentForm)
    else
         PooledMessageDispatcher.RemoveNotificationReceiver(ParentForm);
   end;
end;

constructor TMessagingInitializer.Create(AOwner: TComponent);
begin
  inherited;
  fEnabled:=True;
  InitializeDispatcher;
end;

procedure TMessagingInitializer.SetEnabled(const Value: Boolean);
begin
  if  not (csDestroying in ComponentState) and (value <> fEnabled) then
   begin
      fEnabled:=Value;
      InitializeDispatcher;
   end;
end;

{ TPooledMesageDispatcher }

procedure TPooledMesageDispatcher.AddNotificationReceiver(
  InComponent: TComponent);
begin
   if ComponentReceiverList.IndexOf(InComponent) =-1 then
   begin
      ComponentReceiverList.Add(InComponent);
      InComponent.FreeNotification(self);
   end;
end;

Procedure TPooledMesageDispatcher.InternalMessage(var Msg:TInternalPurposeMsg);
begin
   var GenPurposeMessage:TGeneralPurposeMsg;
    with GenPurposeMessage do
   begin
        MsgID := Msg.PublicMsgID;
        Filler := Msg.Filler;
         BinaryData:=Msg.BInaryData;
         StringData:=Msg.StringData;
         ObjectData:=Msg.ObjectData;
   end;
    for var ListenerIndex := 0 to ComponentReceiverList.Count-1 do
      begin
         ExternalMessageDispatcher.Target:=ComponentReceiverList[ListenerIndex];
         ExternalMessageChannel.Queue.SendMessage(GenPurposeMessage );
      end;
end;

constructor TPooledMesageDispatcher.Create(AOwner: TComponent);
begin
  inherited;
  ComponentReceiverList:=TObjectList.Create;
  ComponentReceiverList.OwnsObjects:=false;
  InternalMessageDispatcher:=TMessageDispatcherFactory.CreateUIDispatcher;
  InternalMessageDispatcher.Channel:=InternalMessageChannel;
  InternalMessageDispatcher.Target:=Self;
  ExternalMessageDispatcher:=TMessageDispatcherFactory.CreateUIDispatcher;
  ExternalMessageDispatcher.Channel:=ExternalMessageChannel;
  ExternalMessageDispatcher.Target:=Nil;
end;

destructor TPooledMesageDispatcher.Destroy;
   procedure FreeMessageDispatcher(var TheDispatcher:IMessageDispatcher);
   begin
       if TheDispatcher = Nil then
        exit;
       try
         TheDispatcher.Enabled:=False;
         TheDispatcher.Target:=Nil;
         TheDispatcher.Channel:=Nil;
         TheDispatcher:=Nil;
       except
         {$IFDEF DEBUG}
            on E:Exception do
              raise Exception.create('Exception Freeing Dispatcher: '+E.Message);
         {$ENDIF DEBUG}
       end;
   end;

begin
  ComponentReceiverList.Free;
  FreeMessageDispatcher(InternalMessageDispatcher);
  FreeMessageDispatcher(ExternalMessageDispatcher);
  inherited;
end;

procedure TPooledMesageDispatcher.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  if Operation = TOperation.opRemove then
     RemoveNotificationReceiver(AComponent);
  inherited;
end;

procedure TPooledMesageDispatcher.RemoveNotificationReceiver(
  InComponent: TComponent);
begin
    var IndexToDelete:=ComponentReceiverList.IndexOf(InComponent);
    if IndexToDelete >-1 then
      ComponentReceiverList.Delete(IndexToDelete);
end;

Initialization
  ExternalMessageChannel:=TMessageChannelFactory.CreateChannel;
  InternalMessageChannel:=TMessageChannelFactory.CreateChannel;
  PooledMessageDispatcher:=TPooledMesageDispatcher.Create(Nil);
finalization
  ExternalMessageChannel:=Nil;
  InternalMessageChannel:=Nil;
  PooledMessageDispatcher.Free;
end.
@vincentparrett
Copy link
Member

Interesting idea. It does dumb things down somewhat by using a single message type.

My general advice when using this library in a UI is to limit the number of UI dispatchers as they would call sychronise too often otherwise.

When time permits I will see if I can come up with a better solution.

@TheOriginalBytePlayer
Copy link
Author

TheOriginalBytePlayer commented May 27, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants