【问题标题】:Hanging NSTask using waitUntilExit使用 waitUntilExit 挂起 NSTask
【发布时间】:2016-01-30 04:06:12
【问题描述】:

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

【问题讨论】:

    标签: cocoa error-handling nstask


    【解决方案1】:

    请注意,如果通过NSTask 运行的任务填满了输出管道,那么进程将挂起,从而有效地阻止waitUntilExit 返回。

    你可以通过调用来防止这种情况

    [task.standardOutput.fileHandleForReading readDataToEndOfFile];
    

    打电话之前

    [task waitUntilExit];
    

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

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

    https://github.com/lroathe/PipeTest

    【讨论】:

    • 太棒了!感谢您指出这一点 - 为我节省了大量等待悬挂管道的时间..
    • 这救了我。好收获!
    【解决方案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());
    

    【讨论】:

      【解决方案3】:

      这是一个 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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-05-02
        • 1970-01-01
        • 1970-01-01
        • 2010-11-18
        • 1970-01-01
        • 2018-09-06
        • 1970-01-01
        相关资源
        最近更新 更多