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.
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);
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.
Stopping the task is trivial.
procedure TfrmTestOTL.actStopHelloExecute(Sender: TObject);
FHelloTask := nil;
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);
FHelloTask.Comm.Send(MSG_CHANGE_MESSAGE, 'Random ' + IntToStr(Random(1234)));
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);
msg : string;
msgID : word;
msg := task.ParamByName['Message'];
case DSiWaitForTwoObjects(task.TerminateEvent, task.Comm.NewMessageEvent,
if msgID = MSG_CHANGE_MESSAGE then
msg := msgData;
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.