我需要同步使用NSTask,但我发现偶尔我的任务会在'waitUntilExit'命令下挂起。我想知道是否有一种优雅的方式 - 错误处理方法 - 终止挂起的任务,以便我可以重新启动另一个?
答案 0 :(得分:17)
请注意,如果通过NSTask
运行的任务填充输出管道,则该过程将挂起,从而有效阻止waitUntilExit
返回。
您可以通过调用
来防止出现这种情况[task.standardOutput.fileHandleForReading readDataToEndOfFile];
之前打电话
[task waitUntilExit];
这将导致输出管道的数据被读取,直到写入输出管道的过程关闭它为止。
演示问题的示例代码和各种解决方案:
答案 1 :(得分:2)
您可以使用-[task launch]
启动任务,然后定期轮询其isRunning
属性以检查它是否已完成。如果在给定的时间间隔后没有完成,您可以调用-[task terminate]
来终止它。这要求您启动的任务不会忽略SIGTERM信号。
但是,如果在您的情况下轮询任务终止效率太低,则可以在启动任务后设置类型为DISPATCH_SOURCE_TYPE_PROC
的调度源。然后,当任务终止时,此源异步调用其事件块:
dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, task.processIdentifier, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
答案 2 :(得分:0)
这是一个NSTask类别,它为您提供了一个超时友好的waitUntilExit。它还允许您安排SIGTERM和SIGKILL。
此方法旨在1)永远无法旋转,2)确保过程被杀死。
@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