Documentation Archive Developer
Search

ADC Home > Reference Library > Technical Q&As > Legacy Documents > Carbon >

Legacy Documentclose button

Important: This document is part of the Legacy section of the ADC Reference Library. This information should not be used for new development.

Current information on this Reference Library topic can be found here:

RunApplicationEventLoop and Thread Manager


Q: I want to change my Carbon application to use RunApplicationEventLoop. My application uses cooperative (Thead Manager) threads and there's no obvious place to call YieldToAnyThread (r. 2729795). Should I use a Carbon timer to yield to my cooperative threads?

A: No. Using a timer is not the best solution because it limits the amount of processing that your cooperative threads can do. The right thing to do depends on whether your threads are compute-bound or I/O-bound. I'll explain each case in turn.

For compute-bound cooperative threads the simplest thing to do is to write your own equivalent of RunApplicationEventLoop that includes a call to YieldToAnyThread. The core code for RunApplicationEventLoop is shown in Listing 3-10 of Inside Carbon: Handling Carbon Events. I've included a version of this code in Listing 1. The EventLoopEventHandler routine is the actual event loop code. The remainder of Listing 1 implements a trick to ensure that the standard handlers are installed while EventLoopEventHandler executes; see the comments in the code for an explanation of what it does and why.



extern SInt32 gNumberOfRunningThreads;
    // This variable must be maintained by your thread scheduling
    // code to accurately reflect the number of threads that are
    // ready and need time for computation.

static EventHandlerUPP gQuitEventHandlerUPP;   // -> QuitEventHandler

static pascal OSStatus QuitEventHandler(EventHandlerCallRef inHandlerCallRef,
                                        EventRef inEvent, void *inUserData)
    // This event handler is used to override the kEventClassApplication/
    // kEventAppQuit event while inside our event loop (EventLoopEventHandler).
    // It simply calls through to the next handler and, if that handler returns
    // noErr (indicating that the application is doing to quit), it sets
    // a Boolean to tell our event loop to quit as well.
{
    OSStatus err;

    MoreAssertQ(inUserData != nil);

    MoreAssertQ( GetEventClass( inEvent ) == kEventClassApplication );
    MoreAssertQ( GetEventKind ( inEvent ) == kEventAppQuit );

    err = CallNextEventHandler(inHandlerCallRef, inEvent);
    if (err == noErr) {
        *((Boolean *) inUserData) = true;
    }

    return err;
}

static EventHandlerUPP gEventLoopEventHandlerUPP;   // -> EventLoopEventHandler

static pascal OSStatus EventLoopEventHandler(EventHandlerCallRef inHandlerCallRef,
                                             EventRef inEvent, void *inUserData)
    // This code contains the standard Carbon event dispatch loop,
    // as per "Inside Macintosh: Handling Carbon Events", Listing 3-10,
    // except:
    //
    // o this loop supports yielding to cooperative threads based on the
    //   application maintaining the gNumberOfRunningThreads global
    //   variable, and
    //
    // o it also works around a problem with the Inside Macintosh code
    //   which unexpectedly quits when run on traditional Mac OS 9.
    //
    // See RunApplicationEventLoopWithCooperativeThreadSupport for
    // an explanation of why this is inside a Carbon event handler.
    //
    // The code in Inside Mac has a problem in that it quits the
    // event loop when ReceiveNextEvent returns an error.  This is
    // wrong because ReceiveNextEvent can return eventLoopQuitErr
    // when you call WakeUpProcess on traditional Mac OS.  So, rather
    // than relying on an error from ReceiveNextEvent, this routine tracks
    // whether the application is really quitting by installing a
    // customer handler for the kEventClassApplication/kEventAppQuit
    // Carbon event.  All the custom handler does is call through
    // to the previous handler and, if it returns noErr (which indicates
    // the application is quitting, it sets quitNow so that our event
    // loop quits.
    //
    // Note that this approach continues to support QuitApplicationEventLoop,
    // which is a simple wrapper that just posts a kEventClassApplication/
    // kEventAppQuit event to the event loop.
{
    OSStatus        err;
    OSStatus        junk;
    EventHandlerRef installedHandler;
    EventTargetRef  theTarget;
    EventRef        theEvent;
    EventTimeout    timeToWaitForEvent;
    Boolean         quitNow;
    static const EventTypeSpec eventSpec = {kEventClassApplication, kEventAppQuit};

    quitNow = false;

    MoreAssertQ(gQuitEventHandlerUPP != nil);

    // Install our override on the kEventClassApplication, kEventAppQuit event.

    err = InstallEventHandler(GetApplicationEventTarget(), gQuitEventHandlerUPP,
                              1, &eventSpec, &quitNow, &installedHandler);
    if (err == noErr) {

        // Run our event loop until quitNow is set.

        theTarget = GetEventDispatcherTarget();
        do {
            if (gNumberOfRunningThreads == 0) {
                timeToWaitForEvent = kEventDurationForever;
            } else {
                timeToWaitForEvent = kEventDurationNoWait;
            }
            err = ReceiveNextEvent(0, NULL, timeToWaitForEvent,
                                   true, &theEvent);
            if (err == noErr) {
                (void) SendEventToEventTarget(theEvent, theTarget);
                ReleaseEvent(theEvent);
            }
            if (gNumberOfRunningThreads > 0) {
                (void) YieldToAnyThread();
            }
        } while ( ! quitNow );

        // Clean up.

        junk = RemoveEventHandler(installedHandler);
        MoreAssertQ(junk == noErr);
    }

    // So we can tell when our event loop quit.

    SysBeep(10);

    return err;
}

static void RunApplicationEventLoopWithCooperativeThreadSupport(void)
    // A reimplementation of RunApplicationEventLoop that supports
    // yielding time to cooperative threads.  It relies on the
    // rest of your application to maintain a global variable,
    // gNumberOfRunningThreads, that reflects the number of threads
    // that are ready to run.
{
    static const EventTypeSpec eventSpec = {'KWIN', 'KWIN' };
    OSStatus        err;
    OSStatus        junk;
    EventTargetRef  appTarget;
    EventHandlerRef installedHandler;
    EventRef        dummyEvent;

    dummyEvent = nil;

    // Create a UPP for EventLoopEventHandler and QuitEventHandler
    // (if we haven't already done so).

    err = noErr;
    if (gEventLoopEventHandlerUPP == nil) {
        gEventLoopEventHandlerUPP = NewEventHandlerUPP(EventLoopEventHandler);
    }
    if (gQuitEventHandlerUPP == nil) {
        gQuitEventHandlerUPP = NewEventHandlerUPP(QuitEventHandler);
    }
    if (gEventLoopEventHandlerUPP == nil || gQuitEventHandlerUPP == nil) {
        err = memFullErr;
    }

    // Install EventLoopEventHandler, create a dummy event and post it,
    // and then call RunApplicationEventLoop.  The rationale for this
    // is as follows:  We want to unravel RunApplicationEventLoop so
    // that we can can yield to cooperative threads.  In fact, the
    // core code for RunApplicationEventLoop is pretty easy (you
    // can see it above in EventLoopEventHandler).  However, if you
    // just execute this code you miss out on all the standard event
    // handlers.  These are relatively easy to reproduce (handling
    // the quit event and so on), but doing so is a pain because
    // a) it requires a bunch boilerplate code, and b) if Apple
    // extends the list of standard event handlers, your application
    // wouldn't benefit.  So, we execute our event loop from within
    // a Carbon event handler that we cause to be executed by
    // explicitly posting an event to our event loop.  Thus, the
    // standard event handlers are installed while our event loop runs.

    if (err == noErr) {
        err = InstallEventHandler(GetApplicationEventTarget(), gEventLoopEventHandlerUPP,
                                  1, &eventSpec, nil, &installedHandler);
        if (err == noErr) {
            err = MacCreateEvent(nil, 'KWIN', 'KWIN', GetCurrentEventTime(),
                                  kEventAttributeNone, &dummyEvent);
            if (err == noErr) {
                err = PostEventToQueue(GetMainEventQueue(), dummyEvent,
                                  kEventPriorityHigh);
            }
            if (err == noErr) {
                RunApplicationEventLoop();
            }

            junk = RemoveEventHandler(installedHandler);
            MoreAssertQ(junk == noErr);
        }
    }

    // Clean up.

    if (dummyEvent != nil) {
        ReleaseEvent(dummyEvent);
    }

Listing 1. RunApplicationEventLoopWithCooperativeThreadSupport



The timeToWaitForEvent variable is a key part of EventLoopEventHandler. If there are no active cooperative threads, timeToWaitForEvent is set to kEventDurationForever which means that ReceiveNextEvent will block your process until some event arrives (or a timer fires). On the other hand, if there are active cooperative threads then timeToWaitForEvent is set to kEventDurationNoWait and ReceiveNextEvent will return immediately with no event, and your cooperative threads will get an appropriately large fraction of the CPU.

Note: Of course, if your cooperative threads are compute-bound you probably want to switch to one of the preemptive threading APIs to take advantage of the second processor on dual processor computers. However, for the sake of this discussion I'll assume that you're looking for the simplest solution.

The approach described above falls down for I/O-bound threads for two reasons.

  • Determining gNumberOfRunningThreads becomes difficult as threads start and stop waiting for I/O.
  • Cooperative threads always have a high scheduling latency, which makes them incompatible with fast I/O (without using excessive memory or CPU). For a further discussion of this point, see the documentation that comes with the OTMP DTS sample.

The solution to this conundrum is to turn your I/O-bound cooperative threads into preemptive threads (MP tasks for Mac OS 9 and Mac OS X, or pthreads for Mac OS X only). A significant amount of the I/O subsystem can be called from preemptive threads, even on Mac OS 9. DTS Technote 2006 MP-Safe Routines lists the preemptive-safe routines that are available on both Mac OS 9 and Mac OS X.

If a particular I/O subsystem is not callable from preemptive threads on Mac OS 9, it may be possible to construct glue for calling it. A good example of this is the "OTMP" DTS sample (see the URL above).


[Oct 10 2001]