【发布时间】:2016-01-30 04:06:12
【问题描述】:
我需要同步使用 NSTask,但是我发现有时我的任务会在“waitUntilExit”命令下挂起。我想知道是否有一种优雅的方式——一种错误处理方法——来终止挂起的任务,以便我可以重新启动另一个?
【问题讨论】:
标签: cocoa error-handling nstask
我需要同步使用 NSTask,但是我发现有时我的任务会在“waitUntilExit”命令下挂起。我想知道是否有一种优雅的方式——一种错误处理方法——来终止挂起的任务,以便我可以重新启动另一个?
【问题讨论】:
标签: cocoa error-handling nstask
请注意,如果通过NSTask 运行的任务填满了输出管道,那么进程将挂起,从而有效地阻止waitUntilExit 返回。
你可以通过调用来防止这种情况
[task.standardOutput.fileHandleForReading readDataToEndOfFile];
打电话之前
[task waitUntilExit];
这将导致读取输出管道的数据,直到写入输出管道的进程关闭它。
演示问题和各种解决方案的示例代码:
【讨论】:
您可以使用-[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());
【讨论】:
这是一个 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
【讨论】: