Wednesday, July 02, 2008

OmniThreadLibrary Example #2: Hello, world!

imageLast time I've shown how to write a trivial threaded Beep in two lines.  Today I'll do something more complicated - a threaded Hello, world program that communicates with its owner. Communication will be unidirectional; I'll leave bidirectional communication for the next example.

First, compile and install the OmniThreadLibrary_D2007 package (from the packages\ subfolder). This will add one component to your palette - TOmniTaskEventDispatch. This component simplifies communication between a threaded task and primary thread.

As before, start with an empty form and drop a button on it. You'll also need a listbox to show "Hello, world!" messages. You'll also need to drom one TOmniTaskEventDispatch component on the form.

Write an OnClick handler for the button.

procedure TfrmTestOTL.btnHelloClick(Sender: TObject);
btnHello.Enabled := false;
OmniTaskEventDispatch1.Monitor(CreateTask(RunHelloWorld, 'HelloWorld')).Run;

The code is quite similar to the one you had to write in the first example, with one important modification - it passes the task interface, created in CreateTask, to the TOmniTaskEventDispatch component. That will tell  the component to start monitoring messages from the threaded task.

As you can see, TOmniTaskEventDispatch.Monitor takes as a parameter the interface returned from CreateTask (I'll give more attention to this interface in latter posts) and it also returns that same interface as its result so that you can immediately call the Run method.

The code above also disables the button. It will be re-enabled when the threaded task exits.

Let's write the code that does some work now. Method RunHelloWorld will be executed in the background thread.

procedure TfrmTestOTL.RunHelloWorld(task: IOmniTask);
task.Comm.Send(0, 'Hello, world!');

The OTL automatically sets up two-way channel between task control interface (the one that is returned from the CreateTask) and the threaded task (the IOmniTask interface passed to the worker method). Both expose a property named Comm that provides this communication. At this time, the communication channel is very simplistic - you can only send (word, Variant) pairs (message ID and data). In this case, I'm setting message ID to 0 and message data to 'Hello, world!'.

That is everything that has to be done on the threaded side. On the GUI side, the OmniTaskEventDispatch1 component is already set to monitor messages from the threaded task. We only have to write an OnTaskMessage handler.

procedure TfrmTestOTL.OmniTaskEventDispatch1TaskMessage(task: IOmniTaskControl);
msgID : word;
msgData: TOmniValue;
task.Comm.Receive(msgID, msgData);
lbLog.ItemIndex := lbLog.Items.Add(Format('[%d/%s] %d|%s', [task.UniqueID, task.Name, msgID, msgData]));

The OnTaskMessage handler is called each time the message is available. The code reads this message and shows it in the listbox. As you can also see, each task is assigned an unique ID.

To enable the button when task is done, you have to write an OnTaskTerminated event handler.

procedure TfrmTestOTL.OmniTaskEventDispatch1TaskTerminated(task: IOmniTaskControl);
lbLog.ItemIndex := lbLog.Items.Add(Format('[%d/%s] Terminated', [task.UniqueID, task.Name]));
btnHello.Enabled := true;

The code doesn't check which task has completed as we can only have one task running.

Run it and press the button few times. You should see that the threaded task gets allocated new unique ID every time it is run.


This example is available in the OTL repository in folder tests\3_HelloWorld_with_package.

If you don't want to install the package, you can create TOmniTaskEventDispatch component on the fly. An example using this approach is available in the repository in folder tests\1_HelloWorld.

No comments:

Post a Comment