|
|
Monitoring an NSTask subprocess
|
|
|
|
Addicted to MacNN
Join Date: Oct 2001
Location: Yokohama, Japan
Status:
Offline
|
|
For a number of reasons, I need to run an untrustworthy program as an NSTask. I say "untrustworthy" because this program can sometimes crash and spit garbage to standard out. I need to save standard out to a file; there is no way around that.
What I've done in other, non-Cocoa implementations of my app is launch the bad program as a background task, and then poll it like so (pseudocode):
Code:
while process is running
if log file is larger than x, do blah blah
else sleep for n seconds
end while
This isn't very good in Cocoa because the C sleep() call hangs the whole app beachball-style. Not sleeping means pegging the processor until the NSTask finishes.
So how can I monitor things like this cleanly? I've looked at run loops and whatnot, but I don't really understand them.
|
|
|
|
|
|
|
|
|
Senior User
Join Date: Feb 2001
Location: Deer Crossing, CT
Status:
Offline
|
|
You could spawn a thread. I use the sleep() function in a thread in my Musical Scales program. The source is available along with the program if you want to poke around in there.
If the log file is pushed through standard out you can use an NSPipe object to redirect standard out. Otherwise you could use some kind of file object (NSFileHandler?) to monitor the log file. You would still need to use threading to keep this sort of monitoring from hanging up your program though.
I thought spawning a new thread was daunting at first, but it turned out to be quite easy.
|
|
|
|
|
|
|
|
|
Forum Regular
Join Date: Feb 2005
Status:
Offline
|
|
Code:
while process is running
if log file is larger than x, do blah blah
else sleep for n seconds
end while
Also, does the program flush it's output sufficiently? If not, you may not see all output.
|
|
|
|
|
|
|
|
|
Dedicated MacNNer
Join Date: Apr 2004
Status:
Offline
|
|
|
|
|
|
|
|
|
|
|
Professional Poster
Join Date: Nov 2000
Location: Tasmania, Australia
Status:
Offline
|
|
Check the Apple documentation for the NSTask class. There is no need to do ANY polling (polling is BAD!!!). Use notifications instead. You can register for NSTask notifications that fire on producing output, or on ending the task, etc. You can also run the Task in such a way as to not proceed to the next step until it finishes executing. It's fairly versatile. Just read the manual.
|
|
|
|
|
|
|
|
|
Addicted to MacNN
Join Date: Oct 2001
Location: Yokohama, Japan
Status:
Offline
|
|
I've read the documentation for NSTask. It only lists NSTaskDidTerminateNotification. Where are all these other notifications you're talking about?
|
|
|
|
|
|
|
|
|
Professional Poster
Join Date: Nov 2000
Location: Tasmania, Australia
Status:
Offline
|
|
Sorry, my post was very misleading, and to be honest, completely incorrect, owing to the fact that I haven't looked at my own code for this for a long time.
What I actually did, was to use NSPipe/NSFileHandle as well as NSTask, and use their notifications to get output, etc (as well as the NSTask notification you mentioned).
I hope this helps a bit more this time.
|
|
|
|
|
|
|
|
|
Addicted to MacNN
Join Date: Oct 2001
Location: Yokohama, Japan
Status:
Offline
|
|
I tried the CocoaDevCentral approach, but Brass's notification-based approach is much better. Thanks.
For posterity, what you do is:
1. Register your application controller or whatever to receive the appropriate notification:
Code:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(myMethod:)
name:@"NSFileHandleReadCompletionNotification"
object:nil];
2. Set the output of your NSTask to a pipe:
Code:
NSTask *task = [[NSTask alloc] init];
NSPipe *pipe = [[NSPipe alloc] init];
[task setStandardOutput:pipe];
3. Get a file handle from the pipe and start the monitoring process:
Code:
NSFileHandle *handle = [pipe fileHandleForReading];
[handle readInBackgroundAndNotify];
4. Begin the task as usual:
[font=Courier New]readInBackgroundAndNotify[/font] is not a repeated thing; it only notifies you once, so in [font=Courier New]myMethod:[/font] you will need to call it again if you want more notifications.
Code:
- (void)myMethod:(NSNotification *)note
{
// Get output string
NSData *data = [[note userInfo] objectForKey:@"NSFileHandleNotificationDataItem"];
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// Do something with string
blah blah blah
// Ask for another notification
if (condition) [[note object] readInBackgroundAndNotify];
[string release];
}
The only snafu I've found is that sometimes the task can output so quickly that [font=Courier New]string[/font] is several lines long. If you're expecting only one line, you can do:
Code:
NSString *oneLineString = [[string componentsSeparatedByString:@"\n"] objectAtIndex:0];
(
Last edited by wataru; Sep 17, 2005 at 10:39 PM.
)
|
|
|
|
|
|
|
|
|
Professional Poster
Join Date: Nov 2000
Location: Tasmania, Australia
Status:
Offline
|
|
Yeah, that looks familiar. Glad I was able to help, after giving you completely the wrong info first time around!
The only other weird thing I've found with this kind of thing is a problem getting the last bit of output when a Task teminates, when there's lots of output, and I'm repeatedly getting notified. I can't remember the exact circumstances where it occured, but there appeared to be some annoying timing problem where the output came AFTER I got notified of the termination, but then I never got notified of the last bit of output.
But in general, it works well.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Forum Rules
|
|
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
|
HTML code is Off
|
|
|
|
|
|
|
|
|
|
|
|