On 2009 Sep 16, at 14:07, Chris Kane wrote:

On Sep 14, 2009, at 6:29 PM, I wrote:

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                         beforeDate:limitTime] ;
// The above will block and be run to here either due to
// the posting of an NSTaskDidTerminateNotification, ...

That is not a valid assumption (described in the comments and apparent from the rest of the code).

Yup. It works in 10.5 but not 10.6. Something is tickling this run loop in 10.5, within a few milliseconds of the NSTaskDidTerminateNotification, so I thought it was the notification itself. Re-reading about Run Loops in Threading Programming Guide [1], I learn that notifications are in fact *not* input sources. The difference in 10.6 is probably explained in the 10.6 Cocoa Foundation Release Notes [2], which I would paraphrase as "run loops which were tickled into running by mysterious input sources in 10.5 may no longer get those tickles in 10.6." Someone please correct me if I misunderstood.

So, I'd like to implement Chris Kane's preferred solution ....

Go back to the main thread. Setup a oneshot NSTimer for the timeout period. Setup a notification handler to listen for the NSTaskDidTerminateNotification. If the timer fires first, kill the task, unregister the notification handler, etc. If the notification happens first, invalidate the timer, unregister the notification handler, etc. Don't run the run loop yourself. Let your code be event-driven.

That would certainly work, but I sometimes need to run this show in a background worker tool, or in secondary thread -- not just in the main thread of an app. How can I detect notifications and use them for controlling program flow in a non-event-driven environment?

To illustrate the problem, I've appended a demo program [3]. Like my original code, the demo works in 10.5 -- the timeout and NSTaskDidTerminateNotification are received *and* the run loop runs...

21:35:56.969 TestTool[366:10b] ver 1.2
21:35:56.982 TestTool[366:10b] Will run run loop
21:35:56.985 TestTool[366:10b]    top of loop
21:35:57.122 TestTool[366:10b] Succeeded : Task with timeout: 1.00 cmd: /bin/sleep 0.10
21:35:57.124 TestTool[366:10b]    moreInputSources = 1
21:35:57.164 TestTool[366:10b]    nTasks = 1
21:35:57.171 TestTool[366:10b]    top of loop
21:35:57.481 TestTool[366:10b] Timed out : Task with timeout: 0.50 cmd: /bin/sleep 0.80
21:35:57.795 TestTool[366:10b]    moreInputSources = 1
21:35:57.797 TestTool[366:10b]    nTasks = 0
21:35:57.798 TestTool[366:10b] All tasks are done.  Exitting.

Running same executable in 10.6, the run loop never runs, and thus the program never exits...

22:05:14.137 TestTool[141:903] ver 1.2
22:05:14.152 TestTool[141:903] Will run run loop
22:05:14.154 TestTool[141:903]    top of loop
22:05:14.254 TestTool[141:903] Succeeded : Task with timeout: 1.00 cmd: /bin/sleep 0.10 22:05:14.643 TestTool[141:903] Timed out : Task with timeout: 0.50 cmd: /bin/sleep 0.80

P.S. Interesting how run loops work. As you can see from the code, after logging 22:05:14 "top of loop", the next statement invokes - runMode:beforeDate: which apparently blocks forever because the NSLog in the following line never logs. But, while it's blocked there, it still receives and processes the NSTaskDidTerminateNotification and timer firing.

Thanks very much,

Jerry Krinock

1. http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#/ /apple_ref/doc/uid/10000057i-CH16-SW19

2. http://developer.apple.com/mac/library/releasenotes/Cocoa/Foundation.html See the end of the second paragraph of the section named "Concurrency, GCD, and Run Loop Cautions (New since November seed)"

3.  Demo Program

/* This program creates two TaskWrapper objects.  Each TaskWrapper
runs an NSTask launching /bin/sleep.  The first one succeeds because
its sleep argument of 0.1 seconds is less than its timeout of 1.0 seconds. The second one times out because its sleep argument of 0.8 seconds is greater
than its timeout time of 0.5 seconds.
*/

#import <Cocoa/Cocoa.h>

static NSMutableArray* activeTaskWrappers ;

@interface TaskWrapper : NSObject {
    NSTask* m_task ;
    NSTimer* m_timeoutTimer ;
    NSString* m_signature ;
    NSString* m_status ;
}

@property (retain) NSTask* task ;
@property (retain) NSTimer* timeoutTimer ;
@property (copy) NSString* signature ;
@property (copy) NSString* status ;

@end

@implementation TaskWrapper

@synthesize task = m_task ;
@synthesize timeoutTimer = m_timeoutTimer ;
@synthesize signature = m_signature ;
@synthesize status = m_status ;

- (void)dealloc {
    [m_task release] ;
    [m_timeoutTimer release] ;
    [m_signature release] ;

    [super dealloc] ;
}

- (void)doShellTaskCommand:(NSString*)command
                 arguments:(NSArray*)arguments
                   timeout:(NSTimeInterval)timeout {
    // Create and configure a task
    NSTask* task = [[NSTask alloc] init] ;
    [self setTask:task] ;
    [task release] ;
    [task setLaunchPath:command] ;
    [task setArguments:arguments] ;
    NSString* signature = [NSString stringWithFormat:
                           @"Task with timeout: %0.2f cmd: %@ %@",
                           timeout,
                           command,
                           [arguments componentsJoinedByString:@" "]] ;
    [self setSignature:signature] ;

    // Add observer for task's self-termination
    [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(taskWrapperDone:) name:NSTaskDidTerminateNotification
                                               object:task] ;

    // Add timer for task timing out
    NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:timeout
                                                      target:self
selector:@selector(timeout:)
                                                    userInfo:nil
                                                     repeats:NO] ;
    [self setTimeoutTimer:timer] ;

    // Launch the task
    [task launch] ;
}

- (void)taskWrapperDone:(NSNotification*)note {
    [[self timeoutTimer] invalidate] ;
    [self setStatus:@"Succeeded"] ;
    NSLog(@"%@ : %@", [self status], [self signature]) ;
    [[NSNotificationCenter defaultCenter] removeObserver:self] ;
    [activeTaskWrappers removeObject:self] ;
}

- (void)timeout:(NSNotification*)note {
    [self setStatus:@"Timed out"] ;
    NSLog(@"%@ : %@", [self status], [self signature]) ;
    [[NSNotificationCenter defaultCenter] removeObserver:self] ;
    [activeTaskWrappers removeObject:self] ;
}

@end


int main(int argc, const char *argv[]) {
    NSLog(@"ver 1.2") ;
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init] ;

    NSString* sleepTimeString ;
    NSTimeInterval timeout ;
    NSTimeInterval sleepTime ;
    NSDate* startDate ;
    TaskWrapper* taskWrapper ;

    activeTaskWrappers = [[NSMutableArray alloc] init] ;

    // Begin a taskWrapper which will succeed
    taskWrapper = [[TaskWrapper alloc] init] ;
    [activeTaskWrappers addObject:taskWrapper] ;
    [taskWrapper release] ;
    timeout = 1.0 ;
    sleepTime = 0.1 ;
    sleepTimeString = [NSString stringWithFormat:@"%0.2f", sleepTime] ;
    startDate = [NSDate date] ;
    [taskWrapper doShellTaskCommand:@"/bin/sleep"
                    arguments:[NSArray arrayWithObject:sleepTimeString]
                      timeout:timeout] ;

    // Begin a taskWrapper which will time out
    taskWrapper = [[TaskWrapper alloc] init] ;
    [activeTaskWrappers addObject:taskWrapper] ;
    [taskWrapper release] ;
    timeout = 0.5 ;
    sleepTime = 0.8 ;
    sleepTimeString = [NSString stringWithFormat:@"%0.2f", sleepTime] ;
    [taskWrapper doShellTaskCommand:@"/bin/sleep"
arguments:[NSArray arrayWithObject:sleepTimeString]
                                 timeout:timeout] ;

    NSLog(@"Will run run loop") ;

// Although it is customary to write the following loop as one line of code,
    // I blew it up so I could understand what was happening.
    while (YES) {
        NSLog(@"   top of loop") ;
BOOL moreInputSources = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]] ;
        NSLog(@"   moreInputSources = %d", moreInputSources) ;
        if (!moreInputSources) {
            break ;
        }

        // The "keep running" condition ...
        NSInteger nTasks = ([activeTaskWrappers count] > 0) ;
        NSLog(@"   nTasks = %d", nTasks) ;
        if (nTasks < 1) {
            break ;
        }
    }

    NSLog(@"All tasks are done.  Exitting.") ;

    [activeTaskWrappers release] ;
    [pool release] ;

    return 0 ;
}

_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to