问题
I need to use NSTask synchronously, however I find that occasionally my task hangs under the 'waitUntilExit' command. I wonder if there is a graceful way--an error handling method--to terminated the hanging task so I can re-launch another?
回答1:
Note that if the task being run via NSTask
fills the output pipe then the process will hang, effectively blocking waitUntilExit
from returning.
You can prevent this situation by calling
[task.standardOutput.fileHandleForReading readDataToEndOfFile];
before calling
[task waitUntilExit];
This will cause the output pipe's data to be read until the process writing to the output pipe closes it.
Example code demonstrating the issue and various solutions:
https://github.com/lroathe/PipeTest
回答2:
You could launch the task using -[task launch]
and then poll periodically on its isRunning
property to check whether it has already finished. If it has not finished after a given time interval, you can call -[task terminate]
to terminate it. This requires that the task you start does not ignore the SIGTERM signal.
If however polling for task termination is too inefficient in your case, you could setup a dispatch source of type DISPATCH_SOURCE_TYPE_PROC
after you have launched your task. This source then asynchronously calls its event block when the task terminates:
dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, task.processIdentifier, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
回答3:
Here's an NSTask category which gives you a timeout-friendly waitUntilExit. It also lets you schedule a SIGTERM and SIGKILL.
This method aims to 1) Never spin indefinitely, and 2) Ensure the process gets killed.
@interface NSTask (TRTaskAdditions_termination)
// This method mitigates the infinite-blocking potential of [NSThread waitUntilExit]
//
// Returns a BOOL indicating whether or not the process exited.
//
// Inputs:
// TO: The initial timeout, during which time we wait for the task to exit. (There are additional timeouts if SENTERM or SENDKILL are YES.
// SENDTERM: If we don't exit during the initial timeout, send SIGTERM and wait 2 seconds.
// SENDKILL: If we still haven't exited, send SIGKILL and wait 2 seconds.
//
// The method runs as follows:
// Step 1: Poll [self isRunning] for (TO) seconds. If the task stops running during that time, we return immediately with YES
// Step 2: If the task didn't exit after (TO) seconds, we send it a SIGTERM if (SENDTERM == YES). Wait 2 seconds for the task to exit.
// Step 3: If the task exits, return YES. If it _still_ hasn't exited (i.e. it ignored the SIGTERM,) send it a SIGKILL if (SENDKILL == YES).
// Step 4: Wait another 2 seconds for the task to end. If it exits, return YES. Otherwise, if the task is still running, return NO.
//
// In theory, setting SENDKILL to YES should terminate any process. Processes aren't supposed to be able to escape signal #9 (KILL).
- (BOOL)waitUntilExitWithTimeout:(CFTimeInterval)TO sendTerm:(BOOL)SENDTERM sendKill:(BOOL)SENDKILL;
@end
#include <signal.h>
@implementation NSTask (TRTaskAdditions_termination)
- (BOOL)waitUntilExitWithTimeout:(CFTimeInterval)TO sendTerm:(BOOL)SENDTERM sendKill:(BOOL)SENDKILL
{
CFAbsoluteTime started;
CFAbsoluteTime passed;
BOOL exited = NO;
started = CFAbsoluteTimeGetCurrent();
for (
CFAbsoluteTime now = started;
!exited && ((passed = now - started) < TO);
now = CFAbsoluteTimeGetCurrent()
)
{
if (![self isRunning])
{
exited = YES;
} else {
CFAbsoluteTime sleepTime = 0.1;
useconds_t sleepUsec = round(sleepTime * 1000000.0);
if (sleepUsec == 0) sleepUsec = 1;
usleep(sleepUsec); // sleep for 0.1 sec
}
}
if (!exited)
{
//NSLog(@"%@ didn't exit after timeout of %0.2f sec", self, TO);
if (SENDTERM)
{
TO = 2; // 2 second timeout, waiting for SIGTERM to kill process
//NSLog(@"%@ sending SIGTERM", self);
[self terminate];
/* // UNIX way
pid_t pid = [self processIdentifier];
kill(pid, SIGTERM);
*/
started = CFAbsoluteTimeGetCurrent();
for (
CFAbsoluteTime now = started;
!exited && ((passed = now - started) < TO);
now = CFAbsoluteTimeGetCurrent()
)
{
if (![self isRunning])
{
exited = YES;
} else {
usleep(100000);
}
}
}
if (!exited && SENDKILL)
{
TO = 2; // 2 second timeout, waiting for SIGKILL to kill process
//NSLog(@"%@ sending SIGKILL", self);
pid_t pid = [self processIdentifier];
kill(pid, SIGKILL);
started = CFAbsoluteTimeGetCurrent();
for (
CFAbsoluteTime now = started;
!exited && ((passed = now - started) < TO);
now = CFAbsoluteTimeGetCurrent()
)
{
if (![self isRunning])
{
exited = YES;
} else {
usleep(100000); // sleep for 0.1 sec
}
}
}
}
return exited;
}
@end
来源:https://stackoverflow.com/questions/33423993/hanging-nstask-using-waituntilexit