


                                                       Chapter 24
                                                   SIMPLE TASKING


WHAT IS TASKING?
_________________________________________________________________

The topic of tasking is probably new to you regardless of what
programming experience you have, because tasking is a relatively
new technique which is not available with most programming
languages.  If you need some kind of a parallel operation with most
other languages, you are required to use some rather tricky
techniques or write a driver in assembly language.  With Ada,
however, tasking is designed into the language and is extremely
easy to use.


BUT NOT TRULY PARALLEL IN ADA
_________________________________________________________________

Tasking is the ability of the computer to appear to be doing two
or more things at once even though it only has one processor.  True
parallel operation occurs when there actually are multiple
processors available in the hardware, but since most modern
computers have only one processor, we must make the system appear
to have more than one by sharing the processor between two or more
tasks.  We will have much more to say about this later, but we must
discuss another topic first.


REAL-TIME REQUIRES TIMING
_________________________________________________________________

In the initial design of the Ada programming      ===============
language, a requirement was included that it be      TIMER.ADA
capable of operating in a real-time environment.  ===============
This requires that we have some control of time.
We at least need the ability to read the current
time and know when we arrive at some specified time.  The example
program named TIMER.ADA will illustrate how we can do just that.

The program begins in our usual way except for the addition of the
new package listed in line 3, the Calendar package which must be
supplied with your compiler if you have a validated Ada compiler.
The specification package for Calendar is listed in section 9.6 of
the LRM and is probably listed somewhere in your compiler
documentation.  This package gives you the ability to read the
system time and date, and allows you to set up a timed delay.
Refer to a listing of the specification package of Calendar and
follow along in the discussion in the next paragraph.




                                                        Page 24-1

                                      Chapter 24 - Simple Tasking

THE CLOCK FUNCTION
_________________________________________________________________

You will notice that the type TIME is private, so you cannot see
how it is implemented, but you won't need to see it.  A call to the
function Clock returns the current time and date to a variable of
type TIME, and other functions are provided to get the individual
elements of the date or the number of seconds since midnight.  You
cannot read the individual elements directly, because some may
change between subsequent reads leading to erroneous data.  A
procedure named Split is provided to split up the type TIME
variable and return all four fields at once, and another is
provided named Time_Of which will combine the individual elements
into a TIME type variable when it is given the four elements as
inputs.

Finally, you are provided with several overloadings of the
addition, subtraction, and some compare operators in order to
effectively use the Calendar package.  A single exception is
declared which will be raised if you attempt to use one of these
subprograms wrong.


THE delay STATEMENT
_________________________________________________________________

The reserved word delay is used to indicate to the computer that
you wish to include a delay at some point in the program.  The
delay is given in seconds as illustrated in line 19 of the program
under study, and is declared as a fixed point number, which is
defined by each implementation.  The value of the delay is of type
DAY_DURATION, but in this case, the universal_real type is used.
The exact definition of the delay is given in appendix F of your
compiler.  It must allow a range of at least 0.0 to 86,400.0 which
is the number of seconds in a day, and it must allow a delta of not
more than 20 milliseconds.  Refer to appendix F of your compiler
documentation to see the exact declaration for this type for your
particular compiler.  The LRM requires that when the delay
statement is encountered, the system must delay at the point of
occurrence for at least the period specified in the delay
statement, but does not say how much longer the system can delay.
This leads to some inaccuracy in the delay which will be up to you
to take care of.  We will see how later in this example program.

A fixed point variable is used for the delay variable so that
addition of times can be done with no loss in accuracy, and fixed
point numbers have a fixed accuracy.

When you execute this program, you will see the first line
displayed on the monitor, then a pause before the second message
is displayed due to the delay statement in line 19.  In fact, the
pause will be at least 3.14 seconds according to the Ada
specification.


                                                        Page 24-2

                                      Chapter 24 - Simple Tasking

USING THE CLOCK FUNCTION
_________________________________________________________________

In line 22, the Clock function is used to return the current time
and date and assign it to the variable named Time_And_Date.  In the
next line we use the procedure named Split to split the time and
date, which is contained in the composite variable named
Time_And_Date, into its various components.  Although the only
component we are interested in is the Seconds field, we must
provide a variable for each of the other fields simply because of
the nature of the procedure call.  Within the function call, we
assign the value of Seconds to Start for later use.  This is a
record of the time when we started the loop which we will use
later.  The time is in the form of the number of seconds that have
elapsed since midnight according to the definition of the calendar
package.

We execute a loop in lines 25 through 38 where we read the time and
date, split it into its component parts, and display each of the
components.  Instead of displaying the time since midnight, we
subtract the Start time from the current time in line 35, where we
are actually using one of the overloadings from the Calendar
package.  We display the elapsed time since we executed line 22 of
this program.  Finally, we put in a total delay of one second each
time we pass through the loop so we can see the delays accumulate.


WE ARE ACCUMULATING ERRORS IN THIS LOOP
_________________________________________________________________

You will recall that the delay statement requires a delay of at
least the amount listed, but says nothing of extra delay allowed
when returning to the program, in order to give the compiler
writers leeway in how to implement the delay statement.  In
addition, we will require some time to execute the other statements
in the loop, so it should not be surprising to you that when you
compile and execute this program, the time will not advance by
exactly one second for each pass through the loop, but will precess
slightly as time passes.


A LOOP WITHOUT ERRORS
_________________________________________________________________

In lines 42 through 51, we essentially repeat the loop but with a
slight difference.  Instead of delaying for one second in each
loop, we delay the amount needed to get to the desired point in
time.  In line 50, we convert the type of Index to DAY_DURATION
with an explicit type conversion, then subtract the current elapsed
time to calculate the desired time of the delay.  This prevents an
accumulation of error, and when you run the program you will see
only the digitizing error introduced by the fixed point number.
However, there is still one potential problem with this method.


                                                        Page 24-3

                                      Chapter 24 - Simple Tasking

WHAT IF A NEGATIVE DELAY IS REQUESTED?
_________________________________________________________________

When calculating delay times like this, it is possible for the
required delay time to result in a negative number.  The Ada
designers had enough foresight to see that in most applications,
you would desire to simply push forward, so they defined the delay
such that a negative value for the delay time would be construed
as a zero, and no error would be raised.  If a negative time should
be considered an error condition for your application, it is up to
you to detect it and issue an appropriate error message, or raise
an exception.

Be sure to compile and execute this program and observe the output.
Due to the delays in the first loop, the data output to the monitor
is somewhat irregular and can be seen when the program is executed.



THE delay IS NOT PART OF CALENDAR
_________________________________________________________________

One final point must be made before we leave this program.  The
Calendar package and the delay statement were both introduced here,
and even though they work well together, they are completely
separate.  The delay statement is not a part of the Calendar
package as will be evidenced in the example programs later in this
chapter.



OUR FIRST TASKING EXAMPLE
_________________________________________________________________

Examine the file named TASK1.ADA for an example   ===============
program containing some tasking.  The first          TASK1.ADA
thing you should examine is the main program      ===============
consisting of a single executable statement in
line 40 that outputs a line of text to the
monitor.  It may seem strange that it doesn't call any of the code
in the declaration part, but it doesn't have to as we shall see.

An Ada task is composed of a task specification and a task body,
the former being illustrated in line 9, and the latter being
illustrated in lines 10 through 17.  This is the first task and
both parts begin with the reserved word task.  The structure of a
task is very similar to the structure of a subprogram or package.
This first example is a very simple task which executes a for loop
containing output statements.  The end result consists of four
lines of text being displayed on the monitor.





                                                        Page 24-4

                                      Chapter 24 - Simple Tasking

THE TASK SPECIFICATION
_________________________________________________________________

The task specification can be much more involved than this one, but
this is a good first example.  The general structure for the task
specification is given by;

     task <task-name> is
           <entry points>;
     end <task-name>;

but if there are no entry points, then only the reserved word task
is needed followed by the task name.  As with the package, the task
specification must come before the task body.


THE TASK BODY
_________________________________________________________________

The task body begins with the reserved words task and body,
followed by the task name, and the remainder of the structure is
identical to that of a procedure.  There is a declarative part
where types, variables, subprograms, packages, and even other tasks
can be declared, followed by the executable part which follows the
same rules as those for a main program.  In this case, there is no
declarative part, only an executable part.  When this task is
executed, it will output four lines to the monitor and stop.  We
will say a little more about when it gets executed in a few
minutes.


TWO MORE TASKS
_________________________________________________________________

It should be clear to you that there are two additional tasks in
this program, one in lines 19 through 27 and another in lines 29
through 37, each with its respective task specification and task
body.  There are actually four tasks, because the main program is
itself another task that will run in parallel with the three
explicitly declared here.  Since it is very critical that you
understand the order of execution of the various tasks, we will
spend some time defining it in detail.

Ada always uses linear declaration and when it loads any program,
it loads things in the order given in the listing.  Therefore when
it finds the task body beginning in line 10, it elaborates all of
its declarations, although there are none in this case, then loads
the executable part of the task, but does not begin execution yet.
It effectively makes the task wait at the begin in line 11 until
the other tasks are ready to begin execution.  It does the same for
the task named Second_Task, and also for Third_Task.  When all
declarations are elaborated it arrives at the begin of the main
program in line 39.  At this point, all four tasks are waiting at
their respective begin statements, and all four tasks are permitted

                                                        Page 24-5

                                      Chapter 24 - Simple Tasking

to begin execution at the same time.  All are of equal priority,
but a strange thing happens when they begin execution.


ORDER OF EXECUTION IS UNDEFINED BY ADA
_________________________________________________________________

The rules of execution, as defined by the LRM, give no requirements
that any form of time slicing be done, nor is it illegal for an
implementation to allow starving to occur.  Starving is where one
task uses all of the available time and the other task or tasks are
allowed to starve because they receive no time for operation.  In
addition, there is no required order of execution concerning which
of the four tasks in our example will execute first, because we
have not included any form of priority.  Because of these rules,
we cannot predict exactly what your implementation will do, but can
only give the results of executing this program on one particular
validated Ada compiler.  As indicated by the results of execution
listed following the program, this particular compiler allows the
task named Third_Task to utilize all computing power until it runs
to completion, then Second_Task uses all of the resources, followed
by First_Task, and finally the main program runs.  Your particular
compiler may execute these statements in a different order, but
that is still correct.  The only requirement is that all 17 lines
be output in any order.  Of course the order of output is defined
within any task according to the rules of sequential operation.
For example, pass number 2 of Third_Task must follow pass 1 of the
same task.


HOW DOES TASKING END?
_________________________________________________________________

When the task named Third_Task arrived at its end statement in line
17, it had executed all of its required statements and had nothing
else to do, so it simply waited there until the other tasks were
completed.  When all four tasks, including the main program task,
had arrived at their respective end statements, the Ada system knew
there was nothing else to do, so it returned to the operating
system as it does at any normal program completion.

Because of the way this program operates, it is not clear that all
four tasks are operating in parallel, but they actually are, as we
will see in the next example program.  Compile and execute this
program and study the output from your compiler to see if it is
different from that obtained as output from our compiler.


ADDING DELAYS ADDS TO THE CLARITY OF OPERATION
_________________________________________________________________

Examine the next example program named TASK2.ADA and you will
notice that delay statements are added within each of the loops.
The delays were chosen to illustrate that each loop is actually

                                                        Page 24-6

                                      Chapter 24 - Simple Tasking

operating in parallel.  The main program is       ===============
identical to the last program, if you ignore the     TASK2.ADA
commented out statements, and since the main      ===============
program has no delay prior to its output
statement, it will be the first to output to the
monitor.  The main program will also be the first to complete its
operation and will then patiently wait at its end statement until
the other three tasks complete their jobs.

Because of the differences in the delays, the three tasks will each
complete their delays at different times and the output should be
very predictable.  If you examine the result of execution given at
the end of the program, you will see that the three tasks actually
are running in parallel as we stated earlier.  It would be
profitable for you to compile and execute this program so you can
observe the output firsthand.



WHAT ABOUT THE MAIN PROGRAM?
_________________________________________________________________

Return once again to the program named TASK2.ADA so we can examine
the main program.  You should remove the comment marks from the
three statements in the main program so that it also contains a
loop and a delay within each pass through the loop.  When you
compile and execute the program as modified, it will execute a
delay prior to the first output, and will output a total of five
lines to the monitor interlaced with the outputs of the other
tasks.  This should be a good indication to you that the main
program is acting just like another task.  Compile and execute this
program, as modified, to see that the main program acts just like
another task.



A BLOCK CAN HAVE TASKS WITHIN IT
_________________________________________________________________

Examine the program named TASK3.ADA for an        ===============
example of a main program with tasks embedded        TASK3.ADA
within it.  There is a block declared in the      ===============
main program in lines 13 through 46 which is
executed inline just like any other block.  This
block however, has the same three tasks we have used in the
previous programs embedded within it.  You will notice that the
order of declaration is different here, since the three task
specifications are given together in lines 14 through 16, but this
is perfectly legal and would be legal in the last two programs
also.  The only requirement is that the task specification must be
given before the task body for each declaring task.




                                                        Page 24-7

                                      Chapter 24 - Simple Tasking

WHAT GOOD IS A DELAY OF ZERO?
_________________________________________________________________

You will notice that all of the delays are of zero time, which may
lead you to ask why we should even bother to include the delays.
Each time the system encounters a delay of any kind, it must look
at each task to see if any of them have timed out and need to be
exercised.  When it does that, it may advance to the next task in
order, but it may not.  Which task will be selected as the next
task is undefined by the Ada LRM, so any task can be executed next,
including the one it is presently executing.  The end result is
that all four tasks, including the one in the executable part of
the block, may be executed in a round robin fashion, and none of
the tasks are starved.  It would be perfectly legal for a single
task to starve the others, according to the LRM, so if your
compiler allows this, it is not an error.  Tasking, as used in a
real programming situation will not have this problem since
additional constructs will be included as we will discuss in the
next few chapters of this tutorial.

In a manner similar to that in the other example programs, when all
four tasks are at their end points, the block is completed, and the
remaining statement is executed in the main program.  Note
carefully that the main program did not enter into the tasking that
was executed within the block.  The inline block was executed as
another sequential statement as part of the main program.  Thus
line 11 was executed, then the block in lines 13 through 46 was
executed.  When all four tasks including the one within the block
body were completed, the output statement in line 48 was allowed
to execute.

Compile and execute this program and study the results.  Use the
results to prove to yourself that the statements in lines 11 and
48 of the main program did not enter into the tasking.


PROGRAMMING EXERCISES
_________________________________________________________________

1.   Write a procedure that can be called from anywhere in the
     program named TIMER.ADA that will use the Seconds field
     returned from the procedure named Split, and display the time
     of day in hours, minutes, and seconds.  Call this procedure
     from a few places within the program.

2.   Modify the program named TASK3.ADA to include the executable
     statements in lines 11 and 48 within loops, with delays, to
     prove that the statements are executed in a sequential fashion
     and do not enter into the tasking which is restricted to the
     block in lines 13 through 46.


                                                        Page 24-8