NOTE: This discussion applies equally well to both the 16-bit SIBO series of computers (Series 3a, etc) and the 32-bit EPOC computers (Series 5, etc).
Your Psion computer (and those based on Psion computers) is a multi-tasking computer. This means that it can do several things at once (strictly speaking it really does one thing at a time, but it can switch between different things so quickly that it looks like several things are happening at once).
As happens, if your program is doing several things at once, you don't want it to stop and wait for one thing before moving on to the next. You want to be able to say "I want to do all these things - let me know when they're done".
On Psion computers, you do this by using asynchronous I/O. This is different from synchronous I/O where you make an I/O request and you have to wait for it to finish. This could be a problem where you would like to do some background processing and wait for a keypress. If the "has a key been pressed?" request was synchronous, your background processing would be stopped until a key was pressed. In other words, your background processing wouldn't really be background processing.
Asynchronous I/O solves this problem. When you make an asynchronous request it immediately returns - before it has finished. Sometime later when your request completes (it could be milliseconds or hours), your Psion computer will send your program a signal to let it know. Your program can then check on the state of the request to see if it completed successfully or if there was an error.
When you make an asynchronous request, it is always a request of another process. The process is usually the operating system of your Psion computer, but could also be other applications. It is never a request to the application doing the requesting. In other words, you cannot make requests to yourself.
The Psion operating system is multi-tasking. This lets the processes that you make requests of to do whatever is needed to handle your request, without stopping your program from going on and doing other things it needs to do.
If your program is doing its thing, and the Psion is doing its thing, how does your program keep track of it's request? It uses a thing called a status word. Every time an asynchronous request is made, information needed for the request is sent to whatever is handling the request. Included with this information is a pointer to the status word. While your request is being handled, the word is set to the value -46. When it is finished it is set to some value other than -46. A zero is normally used to indicate that your request was error-free. Other negative numbers (but not -46) are used to indicate errors, if any occurred. You can look these error numbers up in the Programming Manual.
As mentioned in the Event-Driven Programming tutorial, it is a bad thing for your program to sit in a loop, continuously looking at the status word to see when it finishes. Psion have solved this problem using signals.
Every time a process finishes handling an asynchronous request, it sends the program that made the request a signal. When a program receives a signal, it 'wakes up' and checks the status of all the requests it has made. At least one of them will not be -46 anymore.
It is important to note that when a program is checking its status words, that it handles exactly one status word per signal. The asynchronous requests, signals, and status word handling must all exactly match. It doesn't matter if two requests complete at the same time. They each send a signal (for a total of two signals). The requesting program can handle either signal in any order it chooses, so long as it handles one signal at a time.
Your program makes an asynchronous request, including a pointer to the status word for the request. Some time later the request will complete. The status word will be updated and your program signalled, letting it know its request has completed.
All this theory is a bit dry. What's needed is some code! I'll use the example code in the Programming Manual that performs an asynchronous digital recording. Look for it under "Recording and playing sounds" in the "Advanced Topics" section.
NOTE: The code that does the requests is not included. See your Programming Manual.
PROC record:( file$, time% ) LOCAL sstat% :REM the sound status word LOCAL kstat% :REM the keyboard status word LOCAL key%( 4 ) :REM the buffer to hold the result of the keyboard request LOCAL size% :REM the maximum size of the recording in 0.25sec units LOCAL signals% :REM unhandled signals counter size% = time% * 4 recorda:( ADDR( sstat% ), file$, size% ) :REM async record - see notes below IOC( -2, 1, kstat%, key%() ) :REM async key read - see notes below WHILE 1 IOWAIT :REM wait for a request to complete IF sstat% <> -46 :REM recording has completed IOCANCEL( -2 ) :REM cancel keyboard read - see notes below IOWAITSTAT kstat% IF sstat% < 0 gIPRINT "Error recording:" + ERR$( sstat% ) ENDIF BREAK ELSEIF kstat% <> -46 :REM keyboard read has completed recordc: :REM cancel recording - see notes below IOWAITSTAT sstat% gIPRINT "Cancelled" BREAK ELSE signals% = signals% + 1 ENDIF ENDWH WHILE signals% :REM see notes below IOSIGNAL signals% = signals% - 1 ENDWH ENDP
The code starts off by making two asynchronous requests. One to start
recording (recorda:
), the other to wait for a keypress
(IOC
).
Next, it enters a loop and stops at the IOWAIT
until it is
signalled by an asynchronous request. It will be signalled by one of the two
requests just made, but if this example was part or a larger program, there
could be other asynchronous requests outstanding. In that case, neither
sstat%
or kstat%
will indicate it was the recording or
key requests, and the 'unknown' signal will be counted.
For the example, however, the signal will be from the recording or keyboard
requests. To tell which, the example checks the status words
(sstat%
and kstat%
) in turn. A value of -46 indicates
the request is still pending. Any other value indicates it has completed and the
example should do something about it.
For both cases, the example program cancels the other request, displays a message, and quits.
Before the example exits completely, it enters a loop that restores any
signals that were ignored while it was waiting for the recording to complete or
a key to be pressed. The IOSIGNAL
command is exactly like the
request handlers generating a signal to tell your app that something has
happened. If the example was standing alone, with no other requests, then none
of the signal counting would be required.
You might have noticed that recorda:
and IOC
handled the status word differently. One used ADDR
and one didn't.
The reason can be found in the Programming Manual. Look up IOC
.
It's entry looks like:
r% = IOC( h%, f%, var status%, var a1, var a2 )
The var
before status%
is very
important. Whenever you see var
in the parameter list for
an OPL command, it
means that the address of the variable is passed, and not the value of the
variable. Just pretend that every var
parameter has an
ADDR()
around it. What that means is the value of that variable may
be changed by the command, or (in the case of asynchronous I/O) even after the
command returns by some other process.
When a request is cancelled (using whatever method is required to cancel the
particular request), the cancellation does not, in fact, cancel the request.
What it says is "complete right now!". The request handler then
performs the same processing regardless of if you forced the request to
complete, or if it completed all by itself: the status word is set to a non -46
value and a signal is sent. Since it is known that a signal is going to be sent
immediately, the IOWAITSTAT
command is used to wait for that
signal.
You'll note that the example keeps count of the number of signals it receives that it doesn't know about, and then restores those signals in the loop at the end. This concerned me at first and made me think that signals could occur from things other than my own requests, and that it was important to detect these 'extraneous' signals and forward them on. This was wrong.
The reason the example counts signals is because it assumes that elsewhere in your program you've made some other asynchronous requests that the example doesn't manage. The signal counting is vital to ensure that those other requests are properly handled.
Rather than the hassle of signal counting, the best solution is to have a centralised signal handling system, that detects all signals and passes them to the appropriate handler. The I/O Event Manager module of my library is such a system.
Now that asynchronous I/O has been covered, you might be wondering how it
works with the GETEVENT
command mentioned in the
Event-Driven Programming tutorial. The answer is:
not at all! The GETEVENT
command completely ignores asynchronous
I/O! The next tutorial Asynchronous Event Handling
covers how to have events and I/O working together.