Section 13.2 - Creating and Communicating with Tasks

Let's start by looking at an example of a trivial task. Let's create a type of task that waits for a "Start" request, prints a line of text for a number of times, and then terminates itself.

We'll wait a second between printing each line of text, which will help you see what it does. To make it more interesting, we'll include in the Start request the message to be printed and the number of times it's to print.

First, let's create a task type; the task type will be called Babbler, and we'll enclose it in a package called Babble. Its declaration could look like the following:

  with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;

package Babble is

  task type Babbler is
    entry Start(Message : Unbounded_String; Count : Natural);
  end Babbler;

end Babble;

When declaring a task, an "entry" is somewhat analogous to a procedure declaration. An entry statement declares what requests may be made to the task, including what information may be sent to and from the task when the request is made.

Just like packages and subprograms, tasks have a declaration and a body. The task body could look like this:

with Ustrings;   use Ustrings;

package body Babble is

 task body Babbler is
   Babble : Unbounded_String;
   Maximum_Count : Natural;
 begin
   accept Start(Message : Unbounded_String; Count : Natural) do
       Babble := Message;      -- Copy the rendezvous data to
       Maximum_Count := Count; -- local variables.
   end Start;
   for I in 1 .. Maximum_Count loop
     Put_Line(Babble);
     delay 1.0;       -- Wait for one second.
   end loop;
   -- We're done, exit task.
 end Babbler;

end Babble;

A task body defines what the task will do when it is started up. This particular task simply sets up some local variables and then runs an "accept" statement. An "accept" statement waits for some other task to make a request via the corresponding "entry". When another other task makes the matching request, the accepting task runs the accept statements between the word "do" and the "end" that matches the accept statement. When a task is running the accept statements between "do" and "end", it is said to be in a rendezvous with the other task; the requesting task will not run any instructions until the "end" of the accept statement is run. A common task done in a rendezvous is to copy the data sent by the sending task to a place where the receiving task can use it later. Once the rendezvous is complete, both tasks can run.

Here's a short procedure to demonstrate this task type; we'll call it the procedure Noise. Noise will create two tasks of the given task type and send them Start messages. Note how similar creating a task is to creating a variable from an ordinary type:

with Babble, Ustrings;
use  Babble, Ustrings;
 
procedure Noise is
  Babble_1 : Babbler;  -- Create a task.
  Babble_2 : Babbler;  -- Create another task.
begin
  -- At this point we have two active tasks, but both of them
  -- are waiting for a "Start" message. So, send them a Start.
  Babble_1.Start(U("Hi, I'm Babble_1"), 10);
  Babble_2.Start(U("And I'm Babble_2"), 6);
end Noise;

A procedure that declares a task instance, like procedure Noise, is called a Master. A master must wait for all its tasks to terminate before it can terminate, so Noise will wait until Babble_1 and Babble_2 have exited before it exits.

Note that when procedure Noise makes a ``call'' to Babble_1 and Babble_2's `Start' entry, it is performing a rendezvous.


Quiz:

Should you see the lines of text from Babble_1 and Babble_2 interleaved on your display when you run Noise?

  1. Yes.
  2. No.

You may also:

PREVIOUS Go back to the previous section

NEXT     Skip to the next section

OUTLINE  Go up to lesson 13 outline

David A. Wheeler (dwheeler@dwheeler.com)

The master copy of this file is at "http://www.adahome.com/Tutorials/Lovelace/s13s2.htm".