1

来自loom-lab,给出代码

var virtualThreadFactory = Thread.ofVirtual().factory();

try (var executorService = Executors.newThreadPerTaskExecutor(virtualThreadFactory)) {
    IntStream.range(0, 15).forEach(item -> {
        executorService.submit(() -> {
            try {
                var milliseconds = item * 1000;
                System.out.println(Thread.currentThread() + " sleeping " + milliseconds + " milliseconds");
                Thread.sleep(milliseconds);
                System.out.println(Thread.currentThread() + " awake");
                if (item == 8) throw new RuntimeException("task 8 is acting up");
            } catch (InterruptedException e) {
                System.out.println("Interrupted task = " + item + ", Thread ID = " + Thread.currentThread());
            }
        });
    });
}
catch (RuntimeException e) {
    System.err.println(e.getMessage());
}

我希望代码会catch打印RuntimeException消息,但事实并非如此。

是我希望太多,还是有一天会像我希望的那样工作?


为了回应 Stephen C 的惊人回答,我完全可以理解,在进一步探索后,我通过以下方式发现

static String spawn(
        ExecutorService executorService,
        Callable<String> callable,
        Consumer<Future<String>> consumer
) throws Exception {

    try {
        var result = executorService.submit(callable);
        consumer.accept(result);
        return result.get(3, TimeUnit.SECONDS);
    }
    catch (TimeoutException e) {
        // The timeout expired...
        return callable.call() + " - TimeoutException";
    }
    catch (ExecutionException e) {
        // Why doesn't malcontent get caught here?
        return callable.call() + " - ExecutionException";

    }
    catch (CancellationException e) {   // future.cancel(false);
        // Exception was thrown
        return callable.call() + " - CancellationException";

    }
    catch (InterruptedException e) {    // future.cancel(true);
        return callable.call() + "- InterruptedException ";

    }
}

try (var executorService = Executors.newThreadPerTaskExecutor(threadFactory)) {
    
    Callable<String> malcontent = () -> {
        Thread.sleep(Duration.ofSeconds(2));
        throw new IllegalStateException("malcontent acting up");
    };

    System.out.println("\n\nresult = " + spawn(executorService, malcontent, (future) -> {}));

} catch (Exception e) {
    e.printStackTrace();   // malcontent gets caught here
}

我希望根据文档malcontent被卷入其中,但事实并非如此。因此,我很难对自己的期望进行推理。spawnExecutionException

我对 Project Loom 的大部分希望是,与函数式反应式编程不同,我可以再次依靠异常来做正确的事情,并对它们进行推理,这样我就可以预测会发生什么,而无需运行实验来验证真正发生的事情.

正如史蒂夫乔布斯(在 NeXT)曾经说过的那样:“它就是有效的”

到目前为止,我在 loom-dev@openjdk.java.net 上的帖子还没有得到回复……这就是我使用 StackOverflow 的原因。我不知道吸引 Project Loom 开发人员的最佳方式。

4

3 回答 3

2

这是猜测……但我不这么认为。


根据临时 javadocsExecutorServicenow inherits AutoClosable,并指定该close()方法的默认行为是执行干净关闭并等待它完成。(请注意,这被描述为默认行为而不是必需行为!)

那么为什么他们不能改变行为来捕捉这个线程堆栈上的异常信号呢?

一个问题是,为这种情况以及ExecutorServicetry with resources. 为了实现这种情况下的行为,该close()方法必须由执行程序服务的其他部分通知任务的未处理异常。但是,如果没有任何调用close(),则无法重新引发异常。如果close()在终结器或类似方法中调用了,则可能没有任何东西可以处理它们。至少,它是复杂的。

第二个问题是在一般情况下很难处理异常。如果多个任务因异常而失败怎么办?如果不同的任务因不同的异常而失败怎么办?处理异常的代码是如何处理异常的(例如,您catch (RuntimeException e) ...确定哪个任务失败了?

第三个问题是这将是一个突破性的变化。在 Java 17 及更早版本中,上述代码不会从任务中传播任何异常。在 Java 18 及更高版本中它会。Java 17 代码假设没有来自传递给线程的失败任务的“随机”异常,将会中断。

第四点是,在 Java 18+ 程序员希望将执行程序服务视为资源但不想处理此线程上的“杂散”异常的用例中,这将是一个麻烦。(我怀疑这将是自动关闭执行程序服务的大多数用例。)

第五个问题(如果你想这样称呼它)是对于 Loom 的早期采用者来说这是一个重大变化。(我正在阅读您的问题,说您使用 Loom进行了尝试,但它目前的行为不像您建议的那样。)

最后一个问题是已经有一些方法可以捕获任务的异常并交付它;例如,通过执行任务Future时返回的对象。submit该提案并未填补ExecutorService功能上的空白。

(呸!)


当然,我不知道Java 开发人员是否真的会这样做。在 Loom 最终作为主流 Java 的非预览特性发布之前,我们不会集体知道。

无论如何,如果你想为此游说,你应该通过电子邮件发送给 Loom 邮件列表。

于 2021-10-30T03:01:26.770 回答
1

我认为,最接近您想要实现的目标是

try(var executor = StructuredExecutor.open()) {
    var handler = new StructuredExecutor.ShutdownOnFailure();
    IntStream.range(0, 15).forEach(item -> {
        executor.fork(() -> {
            var milliseconds = item * 100;
            System.out.println(Thread.currentThread()
                + "sleeping " + milliseconds + " milliseconds");
            Thread.sleep(milliseconds);
            System.out.println(Thread.currentThread() + " awake");
            if(item == 8) {
                throw new RuntimeException("task 8 is acting up");
            }
            return null;
        }, handler);
    });

    executor.join();

    handler.throwIfFailed();
}
catch(InterruptedException|ExecutionException ex) {
    System.err.println("Caught in initiator thread");
    ex.printStackTrace();
}

它将在虚拟线程中运行所有作业,并在其中一个作业失败时在启动器线程中生成异常。StructuredExecutor是 Loom 项目引入的一个新工具,它允许在诊断工具中显示创建的虚拟线程对这个特定工作的所有权。但请注意,它close()不会等待完成,而是要求所有者在关闭之前执行此操作,如果开发人员未能这样做,则会引发异常。

经典ExecutorService实现的行为不会改变。

的解决方案ExecutorService

try(var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    var jobs = executor.invokeAll(IntStream.range(0, 15).<Callable<?>>mapToObj(item ->
        () -> {
            var milliseconds = item * 100;
            System.out.println(Thread.currentThread()
                + " sleeping " + milliseconds + " milliseconds");
            Thread.sleep(milliseconds);
            System.out.println(Thread.currentThread() + " awake");
            if(item == 8) {
                throw new RuntimeException("task 8 is acting up");
            }
            return null;
        }).toList());

    for(var f: jobs) f.get();
}
catch(InterruptedException|ExecutionException ex) {
    System.err.println("Caught in initiator thread");
    ex.printStackTrace();
}

请注意,在invokeAll等待所有作业完成时,我们仍然需要循环调用get来强制ExecutionException在启动线程中抛出一个。

于 2021-12-01T18:28:06.330 回答
1

ExecutorServiceLOOM 进行了许多改进,例如,它简化了编码,消除了对/的AutoClosable调用。shutdownawaitTermination

您对简洁异常处理的期望的观点适用ExecutorService于任何 JDK 中的典型用法——不仅仅是即将发布的 LOOM 版本——因此 IMO 显然没有必要与 LOOM 工作联系在一起。

通过在代码块周围添加几行代码,您希望的错误处理很容易与任何版本的 JDK 结合使用ExecutorService

var ex = new AtomicReference<RuntimeException>();
try {
    // add any use of ExecutorService here
    
    // eg OLD JDK style:
    // var executorService = Executors.newFixedThreadPool(5);

    try (var executorService = Executors.newThreadPerTaskExecutor(virtualThreadFactory)) {
       ...
          if (item == 8) {
              // Save exception before sending:
              ex.set(new RuntimeException("task 8 is acting up"));
              throw ex.get();
          }
       ...
    }
    // OR: not-LOOM JDK call executorService.shutdown/awaitTermination here

    // Pass on any handling problem
    if (ex.get() != null)
        throw ex.get();
}
catch (Exception e) {
    System.err.println("Exception was: "+e.getMessage());
}

不像您希望的那样优雅,但适用于任何 JDK 版本。

编辑关于您编辑的问题:

您已将callable.call()代码作为代码放入catch (ExecutionException e) {其中,因此您丢失了第一个异常并malcontent引发了第二个异常。添加System.out.println查看原文:

 catch (ExecutionException e) {
     System.out.println(Thread.currentThread()+" ExecutionException: "+e);
     e.printStackTrace();

     // Why doesn't malcontent get caught here?
     return callable.call() + " - ExecutionException";
 }
于 2021-10-31T18:40:47.483 回答