[Skip top navbar]

Andrew Gregory's Web Pages

View from Mt Beadell, 25°32'1"S 125°16'37"E

-

Asynchronous I/O


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).

Introduction

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.


How It Works

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.

Summary

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.


Some Code

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.

Notes

Passing the Status Word Pointer

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.

Cancelling a Request

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.

Counting Signals

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.


Summary

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.


-