Thursday, July 03, 2008

OmniThreadLibrary Example #4: Bidirectional communication, the OTL way

[Please note - today's snapshot is available on the OTL home page.]

image Today's topic is writing message-driven tasks without having to write an event/message processing loop. Of course, the loop is still there, but it is hidden inside the OtlTask unit.

The demo is stored in folder tests\5_TwoWayHello_without_loop. OTL package is required to open the main form.

This time the worker is not a method or procedure, but a whole object. It must either implement IOmniWorker interface or be a descendant of the TOmniWorker class (which implements IOmniWorker).

The worker class will process two messages. When MSG_CHANGE_MESSAGE is received, worker will change internal message text. When MSG_SEND_MESSAGE is received, worker will send this message text to the owner (just like in yesterday's example). We'll also override the Initialize method to set initial message text. Initialize and it's counterpart Cleanup are executed in the context of the background thread executing the task so they can be used to allocate/deallocate thread-sensitive objects.

const
MSG_CHANGE_MESSAGE = 1;
MSG_SEND_MESSAGE = 2;

type
TAsyncHello = class(TOmniWorker)
strict private
aiMessage: string;
public
function Initialize: boolean; override;
procedure OMChangeMessage(var msg: TOmniMessage); message MSG_CHANGE_MESSAGE;
procedure OMSendMessage(var msg: TOmniMessage); message MSG_SEND_MESSAGE;
end;

We can write those two message handlers identical to the way we write Windows message handlers. Message IDs are arbitrary, I usually start with 1.

{ TAsyncHello }

function TAsyncHello.Initialize: boolean;
begin
aiMessage := Task.ParamByName['Message'];
Result := true;
end;

procedure TAsyncHello.OMChangeMessage(var msg: TOmniMessage);
begin
aiMessage := msg.MsgData;
end;

procedure TAsyncHello.OMSendMessage(var msg: TOmniMessage);
begin
Task.Comm.Send(0, aiMessage);
end;

Worker code (above) is trivial, task creation code only slightly less.

procedure TfrmTestOTL.actStartHelloExecute(Sender: TObject);
var
worker: IOmniWorker;
begin
worker := TAsyncHello.Create;
FHelloTask :=
OmniTaskEventDispatch1.Monitor(CreateTask(worker, 'Hello')).
SetIdle(1000, MSG_SEND_MESSAGE).
SetParameter('Delay', 1000).
SetParameter('Message', 'Hello').
Run;
end;

First we create  the worker object and store it in the interface variable. CreateTasks then takes this worker interface instead of worker method/procedure. SetParameters are same as in the previous example (except that the parameter order has been changed to name, value in today's snapshot). The only mystery here is the SetIdle call. It tells the internal event/message loop to send the MSG_SEND_MESSAGE every 1000 milliseconds. [The second parameter - message ID - is optional. When left out, worker's Idle method is called instead.]


A word of warning: SetIdle will be most probably renamed to SetTimer in the next release.


What about MSG_CHANGE_MESSAGE? It is generated in completely the same way as in the previous example.

procedure TfrmTestOTL.actChangeMessageExecute(Sender: TObject);
begin
FHelloTask.Comm.Send(MSG_CHANGE_MESSAGE, 'Random ' + IntToStr(Random(1234)));
end;

Same goes for the task termination - just call FHelloTask.Terminate and set FHelloTask to nil. Worker object will be destroyed automatically.


So, what you say? Simple enough?

4 comments:

  1. Anonymous23:53

    Overall much simpler than using TThread. No need to worry about synchronize or about queuing messages to Queues.

    I am curious to see how you implement the classic patterns of reader/writer and consumer/producer in OTL so I'll keep reading....

    ReplyDelete
  2. Hi, OTL works with DLL or windows service?

    ReplyDelete
    Replies
    1. OTL definitely works with a windows service and it should probably work without a problem with a DLL. (I am using the former in production, but not the latter.)

      Delete
  3. Are the following different or same? which are the correct way?

    FHelloTask := OmniEventMonitor1.Monitor(CreateTask(worker, 'Hello'))

    FHelloTask := CreateTask(worker, 'Hello').MonitorWith(OmniEventMonitor1)

    ReplyDelete