Running run loops in 10.6 (was: Need -[NSTask waitUntilExitOrTimeout:])
Running run loops in 10.6 (was: Need -[NSTask waitUntilExitOrTimeout:])
- Subject: Running run loops in 10.6 (was: Need -[NSTask waitUntilExitOrTimeout:])
- From: Jerry Krinock <email@hidden>
- Date: Thu, 17 Sep 2009 22:59:08 -0700
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 (email@hidden)
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:
This email sent to email@hidden