next up previous contents index
Next: GNAT Implementation Up: Task Types and Objects Previous: Task Types and Objects   Contents   Index


Ada Tasks

In Ada, tasks are objects. Each task has a unique type, which is specified in an object declaration or allocator (an expression of the form "new ...") that causes the creation of the task. Each task type is declared in two separate parts: a task specification and a task body. The specification has a sequence of entry declarations, which define the communications interface of tasks of that type. The body has the rest of the description of the task type [BR85, Section 2]. The Ada Reference Manual defines the full syntax for a task type and body as follows:

   task_type_declaration ::=
      task type Defining_Identifier
                [known_discriminant_part] [is task_definition];

   single_task_declaration ::=
      task defining_identifier [is task_definition];

   task_definition ::=
     [ private
     end [task_identifier]

   task_body ::=
      task body defining_identifier is
      end [task_identifier];

Over time, tasks proceed through various states. A task is initially inactive; upon activation, and prior to its termination it is either blocked (as part of some task interaction) or ready to run. While ready, a task competes for the available execution resources that it requires to run [AAR95, section 9].

Task Creation

A task type can be regarded as a template from which actual tasks are created. Task objects and types can be declared in any declarative part, including task bodies themselves. For any task type, the specification and body must be declared together in the same unit, with the body usually being placed at the end of the declarative part [BW98, chapter 4.1].

A task object can be created either as part of the elaboration of an object declaration occurring immediately within some declarative region, or as part of the evaluation of an allocator (an expression in the form ``new...''). All tasks created by the elaboration of object declarations of a single declarative region (including subcomponents of the declared objects) are activated together. Similarly, all tasks created by the evaluation of a single allocator are activated together [AAR95, section 9].

The execution of a task object has three main active phases [BW98, chapter 4.2]:

  1. Activation -- the elaboration of the declarative part, if any, of the task body (local variables in the body of the task are created and initialized during activation). The Activator identifies the task which created and activated the task.

  2. Normal execution -- the execution of the statements visible within the body of the task.

  3. Finalization -- the execution of any finalization code associated with any objects in its declarative part.

Figure 2.1: Task States.

Figure [*] shows the transitions among these states during task life. The created task is said to be in the Unactivated state. Then the run-time associates a thread of control to this task. If the elaboration of the task fails then the task goes directly to the Terminated state; otherwise the task reaches the Runnable state, and executes the task user code. If this code executes some operation that blocks the task (according to the Ada semantics-- rendezvous, protected operation, or delay statement--, it reaches the Sleep state and later returns to the Runnable state. When the task executes an Ada terminate alternative or finalizes the execution of the Ada user code, it goes to the Terminated state.

A task indicates its willingness to begin finalization by executing its end statement. A task may also begin its finalization as a result of an unhanded exception, or by executing a select statement with a terminate alternative or by being aborted. A finished task is Completed or Terminated depending on whether it has any active dependents [BW98, chapter 4.2].

The Parent is the task on which a task depends. The following rules apply:

When a parent creates a new task, the parent's execution is suspended while it waits for the child to finish activating (either immediately, if the child is created by an allocator, or after the elaboration of the associated declarative part). Once the child has finished its activation, parent and child proceed concurrently. If a task creates another task during its activation, then it must also wait for its child to activate before it can begin execution [BW98, chapter 4.3.1].

There is a conceptual task (called the Environment Task) which is responsible for the program elaboration. (The environment task is generally the operating system thread which initializes the run-time and executes the main Ada subprogram.) Before calling the main procedure of the Ada program, the environment task elaborates all library units referenced to in the main Ada procedure. This elaboration will cause library-level tasks to be created and activated before the main procedure is called.

Task Activation

The following rules apply to task activation [BW98, chapter 4.2.1]:

  1. For static tasks, activation starts immediately after the complete elaboration of the declarative part in which they are defined.

  2. The first statement following the declarative region is not executed until all tasks have finished their activation.

  3. A task need not wait for the activation of other concurrently created tasks before executing its body.

  4. A task may attempt to communicate with another task which, though created, has not yet been activated. The calling task will be delayed until the communication can take place.

  5. If a task object is declared in a package specification, then it commences its execution after the elaboration of the declarative part of the package body.

  6. Dynamic tasks are activated immediately after the evaluation of the allocator (the new operator) which created them.

  7. The task which executed the Ada statement responsible for new tasks creation is blocked until these tasks have finished their activation.

  8. If an exception is raised in the elaboration of a declarative part, then any task created during that elaboration becomes terminated and is never activated. As the task itself cannot handle the exception, the language model requires the parent (creator) task or scope to deal with the situation: the predefined exception Tasking_Error is raised.

  9. The task attribute Callable returns True if the designated task is neither Completed, Terminated nor Callable. (An abnormal task is one that has been aborted). The task attribute Terminated returns True if the named task has terminated.

Task Termination

The Master is the execution of a construct that includes finalization of local objects after it is complete (and after waiting for any local task), but before leaving [AAR95, section 7.6.1(1)]. Each task depends on one or more masters, as follows [AAR95, section 9.3]:

Furthermore, if a task depends on a given master, it is defined as depending on the task that executes the master, and (recursively) on any master of that task [AAR95, section 9.3].

For the Finalization of a master, dependent tasks are first awaited. Then each object whose accessibility level is the same as that of the master is finalized if the object was successfully initialized and still exists. Note that any object whose accessibility level is deeper than that of the master would no longer exist; those objects would have finalized by some inner master. Thus, after leaving a master, the only objects yet to be finalized are those whose accessibility level is not as deep as that of the master [AAR95, section 7.6.1(4)].

Task Abortion

Ada allows tasks abortion by means of the following syntax:

   Abort_Statement ::= abort Task_Name {Task_Name};

Tasks which are aborted are said to become abnormal, and are thus prevented from interacting with any other task. After a task has been marked as abnormal, execution of its body is aborted. This means that the execution of every construct in the task body is aborted, unless it is involved in the execution of an abort-deferred operation. The execution of an abort-deferred operation is allowed to complete before it is aborted [BW98, chapter 10.2]. Task abortion will be analyzed in detail in chapter [*].

Task Identification

Ada tasks have a unique identifier. The Systems Programming Annex [AAR95, Annex C]. provides a mechanism by which a task can obtain its own unique identification which can be passed to other tasks []:

   package Ada.Task_Identification is

      type Task_Id is private;
      Null_Task_Id : constant Task_Id;

      function  "=" (Left, Right : Task_Id) return Boolean;

      function Image (T : Task_Id) return String;

      function Current_Task return Task_Id;

      procedure Abort_Task (T : Task_Id);

      function Is_Terminated (T : Task_Id) return Boolean;

      function Is_Callable (T : Task_Id) return Boolean;

      --  Implementation defined
      . . .
   end Ada.Task_Identification;

As well as this package, the Annex supports two attributes:

Care must be taken when using task identifiers since there is no guarantee that, at some later time, the task will still be active or even in scope [BW98, chapter 4.4].

next up previous contents index
Next: GNAT Implementation Up: Task Types and Objects Previous: Task Types and Objects   Contents   Index
(c) Javier Miranda. Canary Islands (Spain), 2002. Version 1.0