In this tutorial program I'll describe how to launch two tasks that run
asynchronously to the main program using the Tasker class (V1.02) belonging
to the Amiga Foundation Classes package. The main program starts these two
tasks and only waits for the close gadget of its window to be pressed:
then it kills its child tasks before quitting. The code of the two tasks
is contained in the drawsquare()
and drawcircle()
procedures.
I begin with the description of the main()
procedure. The
first instruction we meet, after some DEFs, is storea4()
:
this procedure stores the global data pointer that the E language keeps in
the A4 register. The storea4()
statement is needed if you
want to use in the child task some global variables or if you want to call
from the child task another procedure defined in your program (or in an
external module; this doesn't apply to system calls, obviously). After you
stored the A4 register you have to retrieve it once for any task started
that needs to access global data: you do it by inserting a
geta4()
statement at the very beginning of your task code; you
can see that this instruction is present in both our tasks (I'll describe
them in more detail later).
The next step is opening a simple window that accepts only the
IDCMP_CLOSEWINDOW
message from Intuition (that is to say the
window hears only about its close gadget being pressed): its pointer is
stored in the mainw
variable. If we cannot open this window
we Raise()
a "WIN" exception and exit from the program;
otherwise we get the pointer to the window userport
(up:=mainw.userport
) and then we use the sigbit of this port
to create the signal mask to be used in the Wait() statement
(upsig:=Shl(1,up.sigbit)
). This is the standard way to wait
for a signal coming from a specified port: the upsig
signal
mask uniquely identifies the userport of this window (and the message that
the close gadget has been pressed is expected to arrive through this port).
I suppose you are already familiar with the Intuition mechanism (and some
Exec basic notions), but I'll say more on it when we talk about the Wait()
function. Note that to access the window structure pointed to by the
mainw
variable (to get the userport address) this variable
must be DEFined as PTR TO window
(a general LONG variable it's
not enough); the same goes for the up
variable DEFined as
PTR TO mp
(message port). The definition of
window
is contained in the 'intuition/intuition' module, and
the one of mp
in the 'exec/ports' module.
Then we build our two Tasker objects:
NEW dsquare.tasker('DrawSquare',FALSE)The
dsquare
object is ready to accept data for a task that,
when started, will be called 'DrawSquare'. The FALSE flag says that this
task mustn't be killed when the dsquare
object is ENDed. You
have to supply a name for your task because Exec needs a name to identify
its tasks; on the contrary the flag is optional: by default when you END
the Tasker object the associated task will be deleted (if still present).
The next line
dsquare.code({drawsquare})says that the code of the DrawSquare task starts at the address of the
drawsquare()
procedure. The task stack defaults to 4000
bytes, and I don't need to modify this parameter.NEW dcircle.tasker('DrawCircle') dcircle.code({drawcircle})Note that the two tasks are not running yet.
Now the main()
program needs a message port to communicate
with the child tasks: we create such a port by calling the
buildPort()
procedure
mainport:=buildPort()If something went wrong we
Raise()
the "PORT" exception and
quit from the program; otherwise we build the signal mask for this port in
the same way we did for the userport of the main window:
mainport
is DEFined as PTR TO mp
to access its
sigbit.
We are ready to start the two tasks:
dsquare.start() dcircle.start()
Then we allocate the memory for a message structure and initialize it:
NEW mes setupMsg(mes, SIZEOF mymsg, mainport)The
mes
variable is DEFined as PTR TO mymsg
; note
the definition of the mymsg
structure:
OBJECT mymsg mnode:mn cod:LONG num:LONG ENDOBJECTEvery custom message structure you define must begin with a
mn
(message node) structure followed by whatever you like (the mn
structure is defined in the 'exec/ports' module). After we have allocated
the necessary memory for our message (NEW mes
) we have to
initialize the mn
part: this task is accomplished by the
setupMsg()
procedure. This procedure fills in the
mn
part of mes
with the size of the whole message
structure and the address of the port where the message will be replied
(mainport
).
In the following REPEAT
cycle we wait for the user to press
the mainw close gadget, then we send to the DrawSquare task the message to
close itself and wait for the answer to this message; only when we get this
answer we can safely quit. I'm going to describe in detail the contents of
this cycle.
The line
ssig:=Wait(upsig OR msig)waits for a signal coming from the userport (of
mainw
) or the
message port mainport
: the two ports are identified by their
signal mask, and these masks are ORed because we are expecting a
message from one port OR the other. The main program is put to sleep:
when a signal arrives, the signal mask of the port that received it is
stored in the ssig
variable. So if we bitwise AND
ssig
with the original signal mask of one of our ports we can
detect which port received the signal: if the statement
IF (ssig AND upsig)results to
TRUE
then the signal arrives from the userport,
otherwise it comes from the other port.
The first WHILE
cycle gets all the intuimessages queued to
the userport (the imsg
variable is DEFined as a PTR TO
intuimessage
; the intuimessage
definition is contained
in the 'intuition/intuition' module):
WHILE (imsg:=GetMsg(up))<>NILIf the message class is
IDCMP_CLOSEWINDOW
we send a message to
the DrawSquare task, otherwise we simply reply to the message
ReplyMsg(imsg)Note: you always have to reply to a message to signal that you no longer need the message data and so the sending task can recycle (or dispose) the memory allocated for that message.
mes.cod:=COD_MYMSG mes.num:=-1In the
cod
field we put a number identifing our message type,
so the DrawSquare task can recognize it; in the num
field we
put the command to be sent to the child task: -1 means "kill yourself and
quit". Then we send it:
dsquare.send(mes)The
send()
method knows if dsquare has created a port and, if
it is so, it sends the message mes
to the address of this
port; if it cannot send the message (perhaps because the port doesn't
exist) it returns FALSE
. A task, when started, hasn't got a
port: you have to create it with the buildport()
method
before the task can receive any message. We will see how (and where) to do
it later on when we'll describe the drawsquare()
procedure.The second WHILE
cycle is invoked if the signal was
received from the mainport
: it gets all the messages queued
to the mainport
WHILE (msg:=GetMsg(mainport))<>NILThe
msg
variable is DEFined as PTR TO mymsg
.
Note: we do not use mes
to get the messages from
mainport because mes
still points to a memory area that we are
supposed to dispose before exiting the program; so writing to
mes
means loosing the address of the memory area. Besides,
when we are sure our message has been replied, we can recycle the memory
pointed to by mes
to send another message of the same type
(after having filled in its fields).cod
field contains the number COD_REPLYMYMSG
IF msg.cod=COD_REPLYMYMSGthen we check if it is the answer to the -1 ("kill yourself") command:
IF msg.num=-1 THEN quit:=TRUEif it is so we are satisfied and we quit.
cod
is different from COD_REPLYMYMSG
it's
not the message we were waiting for and we don't know what to do with it,
so we simply reply and return to sleep. Obviuosly we do not reply if the
message is an answer!
Now the DrawSquare task is killing itself, and the main task is going to
dispose all the resources allocated and to kill the DrawCircle task that
has no port (so we cannot tell it to quit).
We first close the mainport
using the
endPort()
procedure
IF mainport THEN endPort(mainport)then we close the DrawCircle task window (I'll explain later why we do it in the main task)
IF dcwin THEN CloseWindow(dcwin)we close the main window
IF mainw THEN CloseWindow(mainw)we dispose the
dsquare
object: remember that the DrawSquare
task is not deleted by this instruction because of the FALSE
flag we used when we created the object (we don't kill the DrawSquare task
because it is already killing itself)
IF dsquare THEN END dsquarewe END the
dcircle
object (the DrawCircle task is killed by
this instruction and the object is disposed)
IF dcircle THEN END dcirclewe dispose the memory allocated for the message
IF mes THEN END mesand, if some exception has been raised (by our program or by the Tasker module) we write in detail what has happened
explain_exception()The
explain_exception()
procedure is contained in the AFC
module 'AFC/explain_exception'.
Now it's time to say something about the two subtasks: I begin with the
drawsquare()
procedure. The first instruction we meet is
geta4()
: we restore the global data pointer in the A4
register because we need to use the dsquare
object. In fact
the general rule is to DEFine a Tasker object as a global variable
because it is used by the main()
task and by the task it
points to: the main()
task usually needs that object to start
the task and to send it some messages, while the child task needs it to
build a message port and to get messages from its port.
Immediately after having retrieved the global data pointer we build a
message port associated with the task:
dsquare.buildport()Now
buildport()
is a method: it works just like the
buildPort()
procedure, but it doesn't return the port address
because it stores such address in the dsquare
object. Next we
open a window and we store the address of its rastport to draw something in
it:
rp:=win.rportWe build the signal mask associated to the message port created:
pp:=dsquare.port() mysig:=Shl(1,pp.sigbit)The
port()
method returns the address of the message port
created with the buildport()
method. Then we wait for a
signal and, when the signal arrives, we check if there's some message
queued at our port. The REPEAT
and the following
WHILE
cycles work the same way as those contained in the
main()
task, so refer to that part of the tutorial for more
informations. The difference is we cannot Wait()
for a signal
because in such a case the DrawSquare task will be put to sleep, and we
need to do some processing while waiting for a message: so I used the
SetSignal()
call to check for a signal
sigs:=SetSignal(0,mysig)Like
Wait()
, the SetSignal()
function returns the
signal mask received, but it doesn't stop the task; the drawback is that it
doesn't clear the signal bit after having received it (you must clear such
a bit to enable your port to receive further signals: the
Wait()
function does this automatically), so we have to do it
by ourselves: the parameters (0,mysig)
mean that the
mysig
signal will be cleared.msg.cod:=COD_REPLYMYMSGand the DrawSquare task will quit if it receives the "kill message":
IF msg.num=-1 THEN quit:=TRUE
The remaining part of code draws randomly some filled rectangles in the window. Remember to close the window and the message port before quitting the task:
IF win THEN CloseWindow(win) dsquare.endport()
The drawcircle()
procedure is even easier: we retrieve the
global data pointer (geta4()
) because we need to use the
dcwin
global variable to store the address of the window we
are going to open. But why don't I use a local variable as I did in the
drawsquare()
procedure? The DrawCircle task doesn't know when
it's time to quit because I'm not going to open a message port for this
task: so the window pointed to by the dcwin
variable will be
closed by the main()
task before deleting the DrawCircle task.
The drawcircle()
procedure then enters an infinite loop and
keeps on drawing circles in its window until the main()
task
stops it.
This is a relatively complex example of the multitasking capabilities of our Amiga and of the Tasker module. To conclude, the Tasker module makes it easier to manage different tasks at a time, but it's no foolproof: you always have to be careful when playing with tasks, because they are probably one of the most difficult parts of the Amiga Exec.