Improve company productivity with a Business Account.Sign Up

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 297
  • Last Modified:

Best way to write a component to que objects

I am starting to design a component which will send SMS messages. The way I would like to design this is to have a component that created a que that contains objects. Each object should be independant once it is created, sending the initial message then receiving status messages about the message. It should also destroy itself when the message has reached it's destination or when a timeout has expired. Each time an event occurs within a que object (e.g. message timed out, message sent successfully etc.), the parent component should be informed and should fire an Event.

I have some ideas about how to do this, but am a little confused as to how I pass the messages status back to the parent component.

Any ideas will be gratefully received along with 300 points to the best suggestion :) Examples would be nice too...
0
thornton_paul
Asked:
thornton_paul
  • 15
  • 10
  • 8
  • +3
1 Solution
 
EpsylonCommented:
Have you checked TObjectQueue?
0
 
EpsylonCommented:
and TObjectList and TObjectStack...

E.g. you can store objects in TObjectList. If you need to store different objects in the list then derive then all from an abstract base class with a virtual method that processes that object.
0
 
egonoCommented:
listening ...
0
The 14th Annual Expert Award Winners

The results are in! Meet the top members of our 2017 Expert Awards. Congratulations to all who qualified!

 
nnbbb09Commented:

Hello Paul,

I may have the wrong end of the stick but here goes...
If you need the child objects to know about their owner ie: the queue then pass a reference to the owning queue in the constructor of the child object. eg :

constructor TSMSMessage.Create(AOwner:TMessageQueue);
begin
  // nb class names are examples
  inherited;
  FOwner := AOwner;
end;

procedure TSMSMessage.TimedOut;
begin
  // example of child object notifying its parent
  FOwner.MessageTimeOut(Self);

end;

// example of procedure type for custom event handler

TSMSEvent = procedure(Sender:TObject;Message:TSMSMessage) of object;

TMessageQueue = class
private
  FOnmessageTimeOut : TSMSEvent;
published
  property OnMessageTimeOut : TSMSEvent read FOnMessageTimeOut write FOnMessageTimeOut;
end;

procedure TMessageQueue.MessageTimeOut(Message:TSMSMessage);
begin
  // example of raising event
  if assigned(FOnMessageTimeOut) then
    FOnMessageTimeOut(Self,Message);

end;


Then the owning queue can be referenced whenever you like. If you decide to do this then you will need to ensure that both TSMSMessage and TMessageQueue are declared in the same unit as they both need to know about each other in their interface sections.

Regards

Jo
0
 
thornton_paulAuthor Commented:
Hi Jo,

The way you suggested is the way I have written it at the moment. The problem I face is that I don't want to expose an event such as FParent.MessageTimeOut to the user. How can I make it available to the Que Object, but not to the user?

Cheers,
Paul
0
 
nnbbb09Commented:
Hi Paul,

Private and protected methods are visible between classes declared in the same unit. So if you put classes in the same unit they will be able to call each others private methods, but code in any other unit will not.

Regards  

Jo
0
 
EpsylonCommented:
> It should also destroy itself when the message has reached it's destination or when a timeout has expired.

Note that an object can't destroy itself and remove itself from a list. So you can't use an event to do that. What you can do is sending a custom message to the main form to signal it to free the object, or something like that.
0
 
thornton_paulAuthor Commented:
Can it not remove itself from the list and then destroy itself?
0
 
nnbbb09Commented:

If the object can reference it's owning queue then it can pass itself as a reference to a procedure in the queue object. If the list is a TObjectList then simply deleting the object's index will cause it to be freed also.

eg :


procedure TMessageQueue.MessageTimeOut(Message:TSMSMessage);
var
  intIndex:integer;
begin
  // FMessages is assumed to be a TObjectList that contains a list of TSMSMessage objects
 
  intIndex := FMessages.IndexOf(Message);
  FMessages.Delete(intIndex);

end;
0
 
EpsylonCommented:
Well removing from the list can be done, but an object can not destroy itself. There's on exception: TThread. It has a FreeOnTerminate property.
0
 
nnbbb09Commented:
My previous example should had read

 if intIndex > -1 then
   Fmessages.Delete(intIndex);

Jo
0
 
EpsylonCommented:
Read the warning in the help on TObject.Free.
0
 
EpsylonCommented:
That's strange. I went through the source code of TObjectList to see what happen with the OwnsObjects parameter of TObjectList.Create. It copies the boolean to FOwnsObjects but then does absolutely nothing with it. Am I missing something? Weird.
0
 
nnbbb09Commented:

procedure TObjectList.Notify(Ptr: Pointer; Action: TListNotification);
begin
  if OwnsObjects then
    if Action = lnDeleted then
      TObject(Ptr).Free;
  inherited Notify(Ptr, Action);
end;
0
 
EpsylonCommented:
I see  |o)

So this means that an object can't remove itself from the list. Correct me if I'm wrong.
0
 
nnbbb09Commented:

Thats right. But the solution I proposed was for the child object to notify the parent queue (passing itself as a paramter) and the parent queue would remove it from the list.
0
 
thornton_paulAuthor Commented:
Okay guys, I'm working on the code now... be patient and I may be back with a few more questions if that's OK...
0
 
thornton_paulAuthor Commented:
OK, I've got most of the queing working. The only problem now is removing the message from the que...

Jo, could you elaborate a little on the following that you posted earlier. I'm not familiar with Notify.

procedure TObjectList.Notify(Ptr: Pointer; Action: TListNotification);
begin
 if OwnsObjects then
   if Action = lnDeleted then
     TObject(Ptr).Free;
 inherited Notify(Ptr, Action);
end;

Cheers
0
 
feloniousCommented:
paul, i think Jo was directing that comment to Epsylon (Epsylon couldn't figure out what TObjectList did with OwnsObjects).. i might be wrong though.


felonious
0
 
EpsylonCommented:
Correct. I was tracing back the code to see if an object in TObjectList can free itself...
0
 
nnbbb09Commented:
Sorry Paul,

That piece of code was from the vcl source for Epsylon.

Jo
0
 
thornton_paulAuthor Commented:
My fault, too much coding! My head's spinning :) I'll get back soon with more comments.
0
 
lsaeCommented:
listening...
0
 
thornton_paulAuthor Commented:
OK, done it... the solution I decided on was to have 2 ques in the que object. The first que hold objects which are in the process of being sent, the second hold the ones that have finished and are waiting to be destroyed. A message object informs the que object when it has completed.

This method seems to work quite nicely.

I guess Jo should get the points, but maybe someone out there (Jo?) could give me some idea about creating the actual message objects as threads. I had a little play with this, but ended up in a right mess :)

/Paul
0
 
nnbbb09Commented:

I'll have a think about and post later (I'm at work at the moment). How many message objects will there be? I can't remember what the recommended max is for threads but it's certainly not high (maybe 16 to 24). If you can post anymore info about what you had in mind it would help.

Jo
0
 
thornton_paulAuthor Commented:
There should never be more that about 10.

When you send an SMS message, it is possible to send another before you have received the result of the first. The first SMS message object must keep track of incoming information about the SMS message and update the user. When the message has been delivered, the thread can be killed.

Is this clear? It's kind of difficult to explain!
0
 
nnbbb09Commented:
Hmmm, I'm still a bit vague. Any change you could post some code?
0
 
nnbbb09Commented:
Do you just need some advice on writing threads?
0
 
thornton_paulAuthor Commented:
I would love advice on writing threads :) Whenever I try it, I always end up in a mass of exceptions! All I want to do is create each message in it's own thread. That way, any waiting done in the message thread (e.g. waiting for incoming information) will not affect the parent thread. That's my idea anyway.
0
 
nnbbb09Commented:

ok. I'll post something later...
0
 
nnbbb09Commented:

I don't know how much you've done with threads but I'll start with the basics.

All threads inherit from abstract class TThread. What you need to do is override and implement the Execute method. If you plan to update any resources in the main VCL thread then you need to use the Synchronize procedure.
Remember to check the Terminated property in any procedures called from the Execute procedure. If its true then exit the procedure.


This is a quick listing for a form unit that contains a button and a label. It has a simple queue component and a message thread class. Hopefully it should demonstrate the basics of writing a thread.

Good luck

Jo

{======================================================}

unit frmthread;

interface

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

type

  TSMSQueue=class(TComponent)
  private
    FList:TObjectlist;
    FintMessageCount: integer;
    FOnMessage: TNotifyEvent;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;
    procedure MessageComplete(Sender:TObject);
    procedure SendMessage;
    property MessageCount : integer read FintMessageCount;
  published
    property OnMessage : TNotifyEvent read FOnMessage write FOnMessage;
  end;

  TSMSThread=class(TThread)
  private
    FQueue:TSMSQueue;
    procedure DoSend;
  protected
    procedure Execute; override;
  public
    Constructor Create(Queue:TSMSQueue);
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure Button1Click(Sender: TObject);
  private
    Queue : TSMSQueue;
    procedure UpdateGUI(Sender:TObject);
  public
  end;
 
var
  Form1: TForm1;

implementation

{$R *.DFM}

{ TSMSThread }

constructor TSMSThread.Create(Queue: TSMSQueue);
begin
  {Thread constructor. The queue object has been passed as a param. It's not
  actually needed here but might be useful in the future}

  FQueue:=Queue;

  {call the inherited constructor. making sure it is created suspended}

  inherited Create(True);

  {Set priority. Set FreeOnTerminate to false as the Queue object will be
  responsible for freeing the thread}

  Priority:=tpLower;
  FreeOnTerminate:=False;
end;

procedure TSMSThread.DoSend;
var
  i:integer;
begin

  {Any procedures that are called from Execute should check for terminated being true
  If it is then bail out}

  if terminated then
    exit;

  {just waste some time}

  for i:=0 to 50 do
  begin
    sleep(100);
  end;

  {When the process is up then call terminate to signal that the thread is done This will cause the MessageComplete procedure in TSMSQueue to execute}

  terminate;
end;

procedure TSMSThread.Execute;
begin
  {This is the main procedure. Make sure you don't call inherited (it's abstract in the base class}
  DoSend;
end;

{ TSMSQueue }

constructor TSMSQueue.Create(AOwner:TComponent);
begin
  {Constructor for a very basic Queue component}

  inherited Create(AOwner);

  {Create list to manage message threads}

  Flist:=TObjectlist.Create;
end;

destructor TSMSQueue.Destroy;
begin
  {tidy up}

  FList.Free;
  inherited;
end;

procedure TSMSQueue.MessageComplete(Sender:TObject);
begin
  {This event handler is attached to every thread and executes when the thread
  is done. All it does is get the index of the thread in the list and then
  deletes the pointer (and frees the thread)}

  if FList.IndexOf(Sender) > -1 then
    FList.Delete(FList.IndexOf(Sender));

  {Decrement the message count and call the event handler}

  Dec(FintMessageCount);

  if assigned(FOnMessage) then
    FOnMessage(Self);

end;

procedure TSMSQueue.SendMessage;
var
  objMessage:TSMSThread;
begin

  {create a thread and add it to the list}

  objMessage:=TSMSThread.Create(Self);
  Flist.add(objMessage);

  {Set the OnTerminate event handler}

  objMessage.OnTerminate:=MessageComplete;

  {Increment the message count and call the event handler}
  inc(FintMessageCount);
  if assigned(FOnMessage) then
    FOnMessage(Self);

  {Start the thread executing}
  objMessage.Resume;

end;

{============== form stuff ================}

procedure TForm1.FormCreate(Sender: TObject);
begin

  {create queue object and assign an event handler to OnMessage}

  Queue := TSMSQueue.Create(Self);
  Queue.OnMessage := UpdateGUI;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  FreeAndNil(Queue);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  {Send a message}

  Queue.SendMessage;
end;

procedure TForm1.UpdateGUI(Sender: TObject);
begin

  {This event handler every time a message is starts / finished sending}

  label1.caption := 'messages='+inttostr(Queue.MessageCount);
end;

end.
0
 
thornton_paulAuthor Commented:
That's great, just what I need :) You've definately got the points, but I'd like to keep the question open a couple of days while I try it out.

Thanks for your help.
Paul
0
 
nnbbb09Commented:
Glad to help.

Jo
0
 
nnbbb09Commented:
Hi Paul,

Is everything going ok with your component?

Jo
0
 
thornton_paulAuthor Commented:
Hi Jo,

Sorry about the delay, been very busy with the project. The queing is working great, but unfortunately I haven't had chance to look into the threading. Thanks for your help. The points are yours :)

Cheers,
Paul
0
 
nnbbb09Commented:
No problem. Just post here if you need anymore help. I'll keep the notifications switched on.

Jo
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Keep up with what's happening at Experts Exchange!

Sign up to receive Decoded, a new monthly digest with product updates, feature release info, continuing education opportunities, and more.

  • 15
  • 10
  • 8
  • +3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now