优雅地退出被阻塞的进程

3
我用下面的代码启动了一个服务(进程)。我的问题如下:
  • 我需要读取进程的输出来确保它已经启动
  • 如果它已经启动,我就返回并且一切都好了
  • 如果由于任何原因它没有启动,那么 while 循环将会无限制地阻塞,因为进程没有输出任何内容

有什么办法可以在我没有得到预期字符串时优雅地退出这个方法吗?

附注:我可以使用 Future 和 get 的超时来实现,但是我认为可能还有更好的方式。

public boolean startService() {
    try {
        ProcessBuilder pb = new ProcessBuilder("service.exe");
        pb.directory(new File("C:/serviceFolder/"));
        pb.redirectErrorStream(true);
        Process p = pb.start();
        BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String line;
        while ((line = reader.readLine()) != null) {
            if (line.toLowerCase().contains("started")) {
                return true;
            }
        }
        return false; //I never get there when it fails
    } catch (IOException e) {
        throw new RuntimeException("Could not start the service.exe process", e);
    }        
}

3
使用超时的Future是更好的方式。 - Bohemian
3个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
2
如果您可以修改服务代码,最好将代码更改为不可启动时不挂起 - 它应该退出并记录错误消息。这样,您的Java代码将按原样工作。 如果您无法修改,除了设置超时之外,没有其他方法,因为您的Java代码没有办法知道发生了什么。 当然,如果您可以修改服务,另一种选择是观察除进程标准输出/错误之外的输出,例如PID文件、错误日志消息或其他内容。例如,如果子进程已经创建了PID文件,您可以安排检查该文件而不是标准输入,但实际上它是相同的概念,只是应用于使用更好/更简单的代码的不同方式。

0

类似这样的代码应该可以工作。基本上,将服务在单独的线程中启动,并创建一个Timer在一定时间后中断它。请注意,计时器任务是一个Daemon,因此如果需要退出,则不应阻止进程。

显然,如果reader.readLine()消耗并丢弃中断,则此方法将无法正常工作。

private static class ServiceRunner implements Runnable {
  // Am I running?
  volatile boolean running = true;
  // My thread.
  volatile Thread thread = Thread.currentThread();

  @Override
  public void run() {
    // Start a timer.
    Timer timer = new Timer("Wait for ServiceRunner to finish.", true);
    // Fire it after 2 seconds.
    timer.schedule(new StopTask(), 2000);
    try {
      // Start the service.
      startService();
    } finally {
      // No longer running.
      running = false;
    }
  }

  class StopTask extends TimerTask {

    @Override
    public void run() {
      if (running) {
        // Interrupt the service runner.
        thread.interrupt();
      }
    }
  }

  public boolean startService() {
    try {
      ProcessBuilder pb = new ProcessBuilder("service.exe");
      pb.directory(new File("C:/serviceFolder/"));
      pb.redirectErrorStream(true);
      Process p = pb.start();
      BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
      String line;
      while ((line = reader.readLine()) != null) {
        if (line.toLowerCase().contains("started")) {
          return true;
        }
      }
      return false; //I never get there when it fails
    } catch (IOException e) {
      throw new RuntimeException("Could not start the service.exe process", e);
    }
  }
}

我还没有测试过这段代码,但它应该可以工作。

您需要进行调整以保留服务是否已启动的信息。


谢谢,但那看起来像是对 future#get 带超时的重新实现。 - assylias
Future.get在超时后会中止操作吗?是的。然而,我同意这可以很容易地通过使用FutureTask来实现。 - OldCurmudgeon
在超时时,它将尝试中断正在运行的任务,在我的情况下会引发InterruptedException异常。 - assylias

0

看起来 Future#get 方法更受欢迎。为了以后参考,我已经按照以下方式修改了代码:

public boolean startService() {

    Callable<Boolean> start = new Callable<Boolean>() {
        @Override
        public Boolean call() throws Exception {
            ProcessBuilder pb = new ProcessBuilder("service.exe");
            pb.directory(new File("C:/serviceFolder/"));
            pb.redirectErrorStream(true);
            Process p = pb.start();
            BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                if (line.toLowerCase().contains("started")) {
                    return true;
                }
            }
            return false;
        }
    };

    ExecutorService executor = Executors.newSingleThreadExecutor();
    Future<Boolean> future = executor.submit(start);

    try {
        return future.get(1, TimeUnit.SECONDS);
    } catch (InterruptedException ignore) {
        Thread.currentThread().interrupt();
        return false;
    } catch (ExecutionException | TimeoutException e) {
        logger.error("Could not start service", e);
        return false;
    } finally {
        executor.shutdownNow();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,