Wednesday, July 02, 2008

OmniThreadLibrary Example #3: Bidirectional communication

image In the third installment of my introduction to the OmniThreadLibrary, I'll show how you can implement bidirectional communication with the threaded task. This time there will be more code to write so you should probably just open example in tests\4_TwoWayHello_with_package folder (or tests\2_TwoWayHello if you don't want to install the OTL package).

For your convenience, I've uploaded snapshot of the current repository state to the projects home address http://code.google.com/p/omnithreadlibrary/.

For the readers that are following my exposé in the browser, here is the screenshot of  today's example in action.

image

There are three buttons - first starts the threaded task, second instructs the task to change the message and thirds stops the task. There are also a TOmniTaskEventDispatch and a TActionList components on a form.

The actStart action is connected to the first button and starts the threaded task.

procedure TfrmTestOTL.actStartHelloExecute(Sender: TObject);
begin
FHelloTask :=
OmniTaskEventDispatch1.Monitor(CreateTask(RunHello, 'Hello')).
SetParameter(1000, 'Delay').
SetParameter('Hello', 'Message').
Run;
end;

The task is created in an already familiar manner (CreateTask output passed to the Monitor method). Then, two named parameters are set. First parameter to the SetParameter method contains a Variant value and second (optional) contains parameter's name.


At the end, Run is called to start the task and task control interface is stored in a field.

strict private
FHelloTask: IOmniTaskControl;

Stopping the task is trivial.

procedure TfrmTestOTL.actStopHelloExecute(Sender: TObject);
begin
FHelloTask.Terminate;
FHelloTask := nil;
end;

When you click the second button (Change message), standard communication channel (introduced in previous installment) is used to send a message containing new text.

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

The OmniTaskEventDispatch1's event handlers are identical to the Hello, world! example.


The part that is new today is the worker code which must receive and process MSG_CHANGE_MESSAGE messages. Today, this code is a normal procedure and not a method (just to show that this option is available, too).

procedure RunHello(task: IOmniTask);
var
msg : string;
msgData: TOmniValue;
msgID : word;
begin
msg := task.ParamByName['Message'];
repeat
case DSiWaitForTwoObjects(task.TerminateEvent, task.Comm.NewMessageEvent,
false, task.ParamByName['Delay'])
of
WAIT_OBJECT_1:
begin
task.Comm.Receive(msgID, msgData);
if msgID = MSG_CHANGE_MESSAGE then
msg := msgData;
end;
WAIT_TIMEOUT:
task.Comm.Send(0, msg);
else
break; //repeat
end;
until false;
end;

RunHello first retrieves the value of the Message parameter. Then it enters an infinite loop (repeat .. until false) in which it waits for either task.TerminateEvent or task.Comm.NewMessageEvent. DSiWin32 is used for brevity only. Normal WaitForMultipleObjects API call could be used insted.


Task.TerminateEvent is signaled in the Terminate method (when user clicks the Stop "Hello" button). In this case, code simply breaks out of the repeat..until loop.


Task.Comm.NewMessageEvent is signaled when a message is waiting in the communication queue. In this case (WAIT_OBJECT_1), message is received and processed.


If nothing was signaled for <value of the 'Delay' parameter> milliseconds, the WAIT_TIMEOUT path is taken and message msg (whatever the current value may be) is sent to the owner where it is displayed.


If you did any threaded pr0gramming then you certainly recognized this loop - this is fairly standard approach when writing multithreaded code. Nothing special here except the messaging subsystem. Still, OTL doesn't stop here. As you'll see in the next example, it is possible to rewrite this code in much simpler way.

3 comments:

  1. Anonymous06:50

    Intersting idea worth of a little looking. Passing info into the thread seems a little klunky tho.

    It isn't

    12 := a, or let 12 =a, or even usually mov 12,ax, so I think you have the parameter name/value concept reverse to both natural patterns and common patterns of usage.

    Message passing after the fact also looks it it needs more encapsulating to lower the coding overhead.

    But aside from that, like I say, definitely interesting.

    ReplyDelete
  2. Hi,

    I am looking forward to the next installment. What I have seen so far looks like you have quite a lot experience with multithreaded applications. Since I am currently maintaining / extending a multithreaded app as well, I am seriously thinking about using your library. Inter thread communication is usually the biggest challenge.

    twm

    ReplyDelete
  3. @anonymous:

    - Yes, I totally agree. I'll probably change SetParameter into two overloads - one with (value) parameter and other with (name, value) pair.

    - As of the message passing - see the next example (most probably published today).

    ReplyDelete