使用waitUntilExit挂起NSTask

时间:2015-10-29 20:29:16

标签: cocoa error-handling nstask

我需要同步使用NSTask,但我发现偶尔我的任务会在'waitUntilExit'命令下挂起。我想知道是否有一种优雅的方式 - 错误处理方法 - 终止挂起的任务,以便我可以重新启动另一个?

3 个答案:

答案 0 :(得分:17)

请注意,如果通过NSTask运行的任务填充输出管道,则该过程将挂起,从而有效阻止waitUntilExit返回。

您可以通过调用

来防止出现这种情况
[task.standardOutput.fileHandleForReading readDataToEndOfFile];

之前打电话

[task waitUntilExit];

这将导致输出管道的数据被读取,直到写入输出管道的过程关闭它为止。

演示问题的示例代码和各种解决方案:

https://github.com/lroathe/PipeTest

答案 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