"No value present"调用"ProcessHandle"的"parent().get()"时


import java.lang.ProcessBuilder.Redirect;
public class Main {    
public static void main(String args[]){    
ProcessBuilder pb = new ProcessBuilder(args); 
try{
Process p = pb.start();
System.out.println("child process is alive: " + p.isAlive());   
System.out.println("child process pid: " + p.pid());
System.out.println("parent of child process: " + p.toHandle().parent().get().pid());
System.out.println("child process is alive: " + p.isAlive());      
p.waitFor();
} catch (Exception e) {
System.out.println("some exception with start process.");
e.printStackTrace();    
}
}
}

在 Ubuntu 18.04 上,我运行该程序以运行外部程序sleep 10而没有问题:

$ java Main  sleep 10
child process is alive: true
child process pid: 20559
parent of child process: 20539
child process is alive: true
$

但是当我运行程序以运行外部程序时echo hello

$ java Main  echo hello
child process is alive: false
child process pid: 18534
some exception with start process.
java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.get(Optional.java:148)
at Main.main(Main.java:11)

为什么它会在第 11 行p.toHandle().parent().get().pid()get()处报告错误?

如果我注释掉第 11 行,它将毫无问题地运行。为什么?

如果是因为孩子在第 11 行呼叫p.toHandle().parent().get().pid()之前退出,

  • 为什么NoSuchElementException发生在get(),而不是更早发生在toHandle()p.toHandle().parent().get().pid()parent()11号线?
  • 为什么在10号线呼叫p.pid()时没有这样的问题?

我该怎么做才能使问题不发生?例如,如何使子进程持续更长时间,以便p.toHandle().parent().get().pid()可以工作,即使我在子进程中运行了一个短暂的程序?

为什么使用echo hello调用时从parent()返回的可选为空?

因为子进程非常短,并且当您到达请求父进程的命令时,子进程不再存在。因此,无法再查询有关该进程的操作系统。

为什么在呼叫getHandle()pid()时不会发生这种情况?

请注意,这些都不是Optional。这意味着 API 保证在其中返回值。实际上,实现是这样的,一旦操作系统创建进程并在pb.start()中返回,就会创建句柄 - 包括进程ID。因此,您有一个句柄,并且您有一个进程 ID - 但当您使用它时,不能保证具有该 ID 的进程处于活动状态。

该文档明确告诉您不要对基础进程的活动性或标识做出假设。

parent()调用当前通过使用子项的 pid 查询操作系统来实现。因此,创建流程时,父级不是流程记录的一部分,并且在您调用它时,可能不再可用。

如何避免这种情况

每当使用Optional时,请勿使用get。特别是没有事先检查它是否存在(并使用isPresent......get被认为是"反模式")。当你看到一个API给你一个Optional,想想如果Optional恰好是空的,你想做什么。不要做出文档不合理的假设。

在这种情况下,您可能希望显示默认消息,以防找不到父级。例如:

System.out.println("parent of child process: "
+ p.toHandle().parent().map(ProcessHandle::pid)
.map(Object::toString)
.orElse("not available"));

或者,您可以使用当前进程的pid作为默认值,或者您可以想出的任何其他方法。或者,如果父母的身份对您的工作是绝对必要的,您可能会抛出不同类型的例外。有关优雅使用它的各种方法,请参阅Optional文档。

没有办法扩展底层进程 - 您可以使用在执行您传递的命令后休眠的 shell 脚本,但我发现这个解决方案充其量是笨拙的。

最新更新