Re: Problem with reading an NSPipe->NSFileHandle to end
Re: Problem with reading an NSPipe->NSFileHandle to end
- Subject: Re: Problem with reading an NSPipe->NSFileHandle to end
- From: Rasmus Skaarup <email@hidden>
- Date: Sun, 11 Apr 2010 22:18:08 +0200
On 11/04/2010, at 21.17, Ken Thomases wrote:
> On Apr 8, 2010, at 9:57 AM, Rasmus Skaarup wrote:
>> [[NSNotificationCenter defaultCenter] addObserver:self
>> selector:@selector(threadPipeReader:)
>> name:NSFileHandleReadCompletionNotification
>> object:nil];
>>
>> [[NSNotificationCenter defaultCenter] addObserver:self
>> selector:@selector(threadTaskStopped:)
>> name:NSTaskDidTerminateNotification
>> object:nil];
>
> The above lines register to for those notifications on _all_ tasks and file handles in the whole process. This is probably not what you want. You should register for those notifications after you've created the pipe (and its file handle) and the task, and you should register on those objects specifically.
I launch multiple processes, and I do a check to see which one is the one I'm getting notified for by doing this in threadPipeReader:
if ( [notification object] == myFileHandle )
(and of course I have more NSTasks and NSFileHandles than myTask and myFileHandle than my example shows).
So unless you strongly discourage this method, it works out pretty well and I am able to distinguish which process is notifying me. The method you suggests forces me to create seperate sub-routines for each invokation.
My programs sole purpose is to launch processes and look at their output.
>> -(void)startMyTask {
>>
>> NSPipe *pipe = [[NSPipe alloc] init];
>>
>> myFileHandle = [pipe fileHandleForReading];
>>
>> [myFileHandle readInBackgroundAndNotify];
>>
>> myTask = [[NSTask alloc] init];
>> [myTask setLaunchPath:launchPath];
>> [myTask setCurrentDirectoryPath:homeDirectory];
>> [myTask setStandardOutput: pipe];
>> [myTask setStandardError: pipe];
>> [myTask setArguments:arguments];
>>
>> [myTask launch];
>
> Are you using garbage collection? If not, then the above code leaks the pipe.
I'm doing garbage collection.
>> -(void)threadPipeReader:(NSNotification *)notification {
>> [... snipped ...]
>> }
>
> That looks reasonable, to the extent that you showed.
Only issue here is that threadPipeReader: does not get called after threadTaskStopped: has been called. Even though output is clearly missing.
>> -(void)threadTaskStopped:(NSNotification *)notification {
>>
>> NSData *data = [myFileHandle availableData];
>>
>> while ([data length] > 0) {
>> NSLog(@"got some more: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
>> NSLog(@"output size: %d", [data length]);
>> data = [myFileHandle availableData];
>> }
>>
>> }
>> <code end>
>>
>> I never got the full output in threadPipeReader, but then I tried to fetch the data in threadTaskStopped - but that only gives some output. Not all the way to the end either.
>
> What you need to do is just mark some internal state so you know the task has exited in threadTaskStopped:. Then, return to the run loop so that you can continue to receive the notifications from the background reads. Eventually, you'll receive the end-of-file marker (a zero-length data). After you've received both the end-of-file and the task termination notification, then you can proceed to make final use of the data and clean up the task (and your registrations with the notification center).
This sounds like the thing I need - however I need a more detailed explanation. I don't know what "return to the run loop" means. Can you give a code example?
But threadTaskStopped: does not need to examine data for the process that exited. In fact I will prefer to have threadPipeReader: continue to get the data fed, even though the process exited. It's not important that the process exited.
> The issue you're encountering is probably because there's both a background read in progress and your attempt to synchronously read in the foreground.
If you think of the availableData calls from threadTaskStopped:, I only put them there because threadPipeReader: didn't get called after threadTaskStopped: did.
> The background read has probably obtained the "missing" data that you're never seeing from the foreground read.
Hmmm, I suspect some more code is needed. The part of the program that executes these NSTasks is a seperate thread:
App.h:
-(id) init {
self = [super init];
// now assigning super init result to self :-)
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(threadPipeReader:)
name:NSFileHandleReadCompletionNotification
object:nil];
// prepare task termination notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(threadTaskStopped:)
name:NSTaskDidTerminateNotification
object:nil];
NSThread *bgThread = [[NSThread alloc] initWithTarget:self selector:@selector(myThread:) object:nil];
[bgThread start];
}
}
- (void)myThread:(id)param {
[self performSelectorOnMainThread:@selector(startMyTask:) withObject:nil waitUntilDone:YES];
}
> If you still aren't getting all of the output you expect, then your task is probably exiting early, perhaps crashing.
I'm stone sure the program is not crashing and I am missing some output. If run by hand it works everytime and output is satisfactory.
Thanks for your time Ken. I much appreciate it - bear with me as I have not yet earned the Master of Cocoa badge.
Br
Rasmus Skaarup_______________________________________________
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