June 95 - Futures: Don't Wait Forever
Futures: Don't Wait Forever
GREG ANDERSON
Futures provide a convenient way to implement asynchronous interapplication
communication without having to manage unwieldy completion routines. This article
presents an updated Futures Package that supports event timeouts, allows threaded
execution of incoming Apple events, and has been revised to work with the Thread
Manager in System 7.5.
Asynchronous Apple-event handling is difficult in Macintosh applications, and
programmers who make the extra effort to implement it often find that detecting and
recovering from event timeouts is an unmanageable task. Code that's written with the
assumption that a completion routine will eventually be called will end up waiting
forever if the event never completes. Futures provide a convenient way to support
asynchronous interapplication communication and handle timeouts in a robust way,
without sacrificing the simplicity or readability of the code.
Most applications attempt to manage multiple concurrent events through callbacks
passed to AESend -- but that leaves you, the application writer, with the burden of
ensuring that the callbacks really do handle every event that's processed by the
application's main event loop. For example, if you're writing an application that sends
events to the Scriptable Finder, and you want to make that application scriptable
itself, you'd have to be particularly careful not to lock up the user interface portion of
your application every time an Apple event was received and processed. But by using
threads, futures, and the asynchronous event-processing techniques described in this
article, you can make the user-interface and event-processing modules of your
application function independently -- and almost without effort on your part.
If you're a long-timedevelop reader, you probably remember Michael Gough's article
on futures that appeared indevelop Issue 7. That article's information is still valid,
and its code runs as well on today's Macintosh computers as it did when first
published; however, it requires the Threads Package that came with Issue 6 in order to
run. Thisarticle presents a revised version of the Futures Package, which works with
the ThreadManager that's now part of System 7.5. We'll also delve a little deeper into
the realm of asynchronous event processing and timeout event handling. And, for the
curious, we'll open the black box and peer inside to examine the inner workings of
futures.
For a review of threads and futures, see "Threads on the Macintosh" in develop
Issue 6, "Threaded Communications With Futures" in Issue 7, and "Concurrent
Programming With the Thread Manager" in Issue 17.*You can use the techniques
described in this article with any application that uses Apple events, but they're
particularly effective with scriptable applications that also send Apple events to other
applications. You'll find the code for the new Futures Package on this issue's CD, along
with the code for the FutureShock example, described later on, and preliminary
documentation for the Thread Manager (eventually to be incorporated intoInside Macintosh: Processes ).
For more on interactions with the Scriptable Finder, see "Scripting the
Finder From Your Application, develop Issue 20.*
OVERVIEW OF FUTURES
For those of you who missed "Threaded Communications With Futures" indevelop Issue
7, a future is a data object that looks and acts just like a real reply to some message,
when in reality it's nothing more than a placeholder for a reply that the server
application will deliver at some future time. (See "Client/Server Review" for a
summary of how clients and servers interact.) Code written to use futures looks the
same as code that waits for the reply to arrive (using a sendMode of kAEWaitReply)
and then works with the actual data. The only difference is that the futures code uses a
timeout value of 0. This causes AESend to return immediately to the caller with a
timeout error -- the normal and expected result -- and execution of the client
application is allowed to continue without delay.
The futures-savvy application then does as much processing as possible without
accessing the reply, including sending other Apple events. When the data from the
reply is absolutely needed, it's accessed as usual via AEGetKeyPtr or some other Apple
Event Manager data-accessor function. It's at this point that the Futures Package steps
in and suspends processing of the client application until the data for the reply
arrives; other parts of the client keep running unhindered. Of course, it's not possible
to stop one part of an application without stopping all of it, unless the application is
multithreaded. Therefore, futures need to run with some sort of Thread Manager.
Figure 1, which appeared originally indevelop Issue 7, summarizes the roles of
threads and futures and the interactions that take place when a client asks a question.
The primary benefit of the Thread Manager and Futures Package is that their use
removes the burden of managing multiple concurrent events, whether they're Apple
events or user actions. As mentioned earlier, most applications try to get around this
problem by providing a callback procedure to AESend that can handle other incoming
Apple events, update events, and user actions while the application is waiting for the
reply. This technique works, but it's up to you to make sure the callbacks handle
everything. Listing 1 shows an example of how the callback approach works; notice
that we need idle and filter procs to handle events that come in while the handler is
waiting for a reply.
Figure 1. The transformation of a future into a real answer
Responding to Apple events without using threads and futures is even more
problematic, particularly if the application needs to send out another message in order
to process the one that just came in (as in Listing 1). In that case, AESend is typically
called again with the same callback procedure, and the whole process stacks up one
level and repeats.
The problem with the stacked approach is threefold: First, the stack must unwind in
the same order in which it was set up -- an ill-timed incoming event, if it's a lengthy
request, could interfere with the processing of the current outgoing request for quite a
while. Second, every stack is finite in size; it's often difficult to prove that reentrant
code will always have enough stack space to complete. Finally, writing callbacks and
having multiple event loops in your application makes the source harder to follow, and
what's more, it's a real drag. By contrast, futures allow the freedom of asynchronous
operation without the drudgery of callbacks or completion routines -- your code looks
as simple as the normal synchronous version, but it runsasynchronously. The only
difference from Listing 1 is that the code calls AskForFutureinstead of AESend, as
follows:
if (err == noErr)
err = AskForFuture(&question, &answer, kAEDefaultTimeout,
kNoMaximumWait, kNormalPriority);
Listing 1. An Apple event handler that sends an event
pascal OSErr AnAEHandler(AppleEvent* ae, AppleEvent* reply,
long refCon)
OSErr err = noErr;
AppleEvent question, answer;
AEAddressDesc target;
DescType typeCode;
long actualSize, result;
// Create an Apple event addressed to a previously determined
// target. 'question' and 'answer' should be set to null
// descriptors.
err = AECreateAppleEvent(kAnEventClass, kAnEventID, &gTarget,
kAutoGenerateReturnID, kAnyTransactionID, &question);
// Call AESend with the send mode kAEWaitReply. Note the idle
// and filter procs.
if (err == noErr)
err = AESend(&question, &answer, kAEWaitReply,
kNormalPriority, kAEDefaultTimeout, gAEIdleProcRD,
gAEFilterProcRD);
if (err == noErr)
err = AEGetParamPtr(&answer, keyAEResult, typeLongInteger,
&typeCode, (Ptr) &result, sizeof(long), &actualSize);
if (err == noErr)
err = AEPutParamPtr(reply, keyAEResult, typeLongInteger,
(Ptr) &result);
AEDisposeDesc(&question);