views:

54

answers:

2

I've run into an while trying to launch an NSTask from inside an NSOperation.

Here's a very simple app I've put together to showcase the problem. When you click the button, an NSOperation is queued. It sets up an NSRunLoop, and calls a method which invokes an NSTask. The task is really simple- it just launches /bin/sleep for two seconds (enough to easily see the spinner when things are working properly).

The app works as advertised, however if you change line 23 of TaskPerformer.m to an autorelease, (sorry, I'm a new poster so I can't link directly) or comment it out entirely (thus leaking the NSTask object), the NSOperation's thread will never exit. Its main runloop seems to be blocking on something.

Now, the problem here is twofold. First off, I don't understand why my thread is blocking, but moreover, if I turn on garbage collection for this application, the same behavior manifests itself. Because there's no way for me to manually release the NSTask, the thread blocks no matter what.

If someone could tell me what's going on, I'd be eternally grateful!

+1  A: 

I see a couple different issues in the sample project you posted. In TaskPerformer.m you have:

[task waitUntilExit];
[task launch];

The waitUntilExit call is meant to be called after launching the task, and will simply block and do nothing until the task has completed. If you only care about waiting until the task has finished, and not about getting output from it or anything, then you should be able to just call launch followed by waitUntilExit, and not have to bother messing with the run loop at all.

If you do want to get output from the task though, then you'll want to get its standardOutput, which by default should return you an instance of NSFileHandle. You can then call readDataOfLength: or readDataToEndOfFile, both of which will block and return data from the process when data is available.

Since this entire thing is going to be done in a background thread anyway, it's OK that these methods block, but you wouldn't want to do the same thing on the main thread, since that would lock up the interface for the user until the task was complete. If you did this on the main thread, you'd want to use NSFileHandle's readInBackgroundAndNotify and friends. For a background thread though, using NSRunLoop shouldn't really be necessary.

Brian Webster
Thanks for pointing that out, Brian. I had already seen that, just hadn't pushed the updated code to Github. Fixed now.Unfortunately, an `NSRunLoop` is necessary in my actual application, because in addition to launching a bunch of `NSTask` s, I also use some asynchronous `NSURLConnection` s which require an `NSRunLoop` for their callbacks.What's also interesting is that removing the call to `waitUntilExit` makes it so the thread exits the first time, but not subsequent times. Something seems really weird here.
texel
A: 

I figured out what was going on here. Turns out my call to [runLoop run] was setting up the loop and running it indefinitely. It was never falling down into the while (!done) loop. Turns out that after calling run, an NSRunLoop will run until it has no more inputs. Calling release on my NSTask led to exactly this scenario, so (quite by accident) my runloop exited.

The solution was to remove [runLoop run] and simply rely on my own while loop. Hope this helps somebody else!

texel