Tasks: Declaration, Activation & Termination

Create concurrent threads of control with single tasks and task types, parameterize them with discriminants, understand activation, masters and termination (the parent waits for its tasks), allocate tasks dynamically, use 'Terminated and 'Callable, and avoid data races by design.

Concurrency is part of the Ada language itself, not a library. A task is an object with its own thread of control; you declare it like a variable and it starts automatically. The enclosing scope (its master) cannot finish until all its tasks have terminated, so you never lose track of a thread. This lesson introduces tasks; the following lessons show how they communicate safely.

Task Types and Single Tasks

A task has a specification (its interface — entries, covered in the next lesson) and a body: 'task type Worker (Id : Positive); task body Worker is ... begin ... end Worker;'. Declaring 'W1 : Worker (1);' creates a task object. A single task ('task Greeter;') declares an anonymous task type with exactly one object. Discriminants pass initial parameters (an Id, a priority, an access to shared data). Arrays and records of tasks are allowed, and tasks are limited objects (no copying).

• task type T (Discriminants) [is entries end T]; task body T is ... end T; • task Name; — a single task. • Tasks are limited objects; arrays of tasks are fine.

Activation, Masters and Termination

Tasks declared in a declarative part are activated together just before the statements after 'begin' execute (after elaboration of the declarations). The subprogram, block or task in which a task is declared is its master: the master waits at its end until all its dependent tasks terminate. That is why the block containing W1 .. W4 acts like a 'join'. A task terminates when its body completes (or when it is aborted, or selects a terminate alternative). An exception unhandled inside a task silently terminates that task only — always add a handler that logs it. Task'Terminated and Task'Callable query the state.

• Activation: just after the declarations are elaborated. • Masters wait for their tasks → structured concurrency. • Unhandled exception in a task: task dies quietly — log it! • 'Terminated, 'Callable attributes.

Dynamic Tasks and Shared Data

'type Worker_Ref is access Worker; X := new Worker (1);' creates a task at run time; it is activated immediately and its master is the scope of the access type. Tasks may read and write shared variables, but unsynchronized concurrent access to the same variable is erroneous (a data race). Safe patterns: each task writes only its own data (like Results (Id)) and the parent reads after the tasks terminate; or communicate via rendezvous or protected objects. GNAT maps each Ada task to an operating-system thread.

• new Task_Type creates tasks dynamically. • Data races are erroneous — partition data or use protected objects. • GNAT: one OS thread per task.

Code Examples

Four workers each sum a quarter of 1.. 1 000 000 into their own slot. The block containing them cannot exit until all four finish, so the main program reads the results safely afterwards. Output (deterministic because only the main task prints): Main: tasks declared here start (activate) before this line runs Main: all four workers have terminated worker 1 sum = 31250125000 worker 2 sum = 93750125000 worker 3 sum = 156250125000 worker 4 sum = 218750125000 Total 1.. 1_000_000 = 500000500000 (formula: 500000500000) Dynamic worker terminated: TRUE Greeter terminated: TRUE task body Worker is begin Total:= Total + Local_Sum; -- shared variable, no protection end Worker;

-- write to Results (Id), or call a protected procedure Total.Add (Local_Sum)

Unprotected concurrent updates are data races.

task body Worker is begin ... -- may raise end Worker;

task body Worker is begin ... exception when E : others => Log (Exception_Information (E)); end Worker;

Unhandled exceptions silently kill tasks.

-- printing from several tasks and expecting a fixed order -- collect results and print from one task, or serialize output through a protected object

Interleaving of independent tasks is nondeterministic.

When does a task declared in a block start running? A. When you call Start B. At activation, just before the block's statements execute C. At program end D. When its first entry is called

Can a block end while its local tasks are still running? A. Yes B. No, the master waits for dependent tasks to terminate C. Only in Ravenscar D. Only with abort

What happens to an unhandled exception inside a task? A. Propagates to main B. The task terminates (silently) C. The program aborts D. It's ignored and the task continues

Which attribute tells whether a task has finished? A. 'Done B. 'Terminated C. 'Finished D. 'Callable

B. ('Callable is False once it is completed or terminated.) Trace the task example.

Real output: Main: tasks declared here start (activate) before this line runs Main: all four workers have terminated worker 1 sum = 31250125000 worker 2 sum = 93750125000 worker 3 sum = 156250125000 worker 4 sum = 218750125000 Total 1 .. 1_000_000 = 500000500000 (formula: 500000500000) Dynamic worker terminated: TRUE Greeter terminated: TRUE

with Ada.Text_IO; use Ada.Text_IO;

procedure L52_Tasks is
   type Result_Array is array (1 .. 4) of Long_Long_Integer;
   Results : Result_Array := [others => 0];

   --  A task type with a discriminant: each worker sums a different slice
   task type Worker (Id : Positive);

   task body Worker is
      Sum  : Long_Long_Integer := 0;
      From : constant Long_Long_Integer := Long_Long_Integer (Id - 1) * 250_000 + 1;
   begin
      for N in From .. From + 249_999 loop
         Sum := Sum + N;
      end loop;
      Results (Id) := Sum;           --  each task writes only its own component
   end Worker;

   --  A single task (anonymous task type)
   task Greeter;
   task body Greeter is
   begin
      null;                          --  finishes immediately
   end Greeter;

   Total : Long_Long_Integer := 0;
begin
   Put_Line ("Main: tasks declared here start (activate) before this line runs");
   declare
      W1 : Worker (1);
      W2 : Worker (2);
      W3 : Worker (3);
      W4 : Worker (4);
   begin
      null;                          --  the block cannot end until W1 .. W4 terminate
   end;
   Put_Line ("Main: all four workers have terminated");
   for I in Results'Range loo
…