【问题标题】:How to signify failure of a Java Future result如何表示 Java Future 结果失败
【发布时间】:2012-12-18 20:01:20
【问题描述】:

AFAIK 提交Callable/RunnableExecutorService 是如果我想并行执行资源密集型代码的方法。因此我的方法结构:

public class ServiceClass {
    protected final ExecutorService executorService = Executors.newCachedThreadPool();

    public Future<Result> getResult(Object params) {
        if (params == null) {
            return null; // In situations like this the method should fail
        }
        // Do other fast pre-processing stuff
        return executorService.submit(new CallProcessResult(params));
    }

    private class CallProcessResult implements Callable<Result> {
        private Object params;
        public CallProcessResult(Object params) {
            this.params = params;
        }
        @Override
        public Result call() throws Exception {
            // Compute result for given params
            // Failure may happen here too!
            return result;
        }
    }
}
public class Result {
    ...
}

我在上面的代码中标记了两个可能发生故障的位置。对于这两种情况,可用于错误处理的选项完全不同。

在提交任务之前可能会出现参数无效、一些可能会失败的快速预处理代码等问题。

我在这里看到了几种表示失败的方式:

  1. 如果提供给getResultparams 无效,则立即返回null。在这种情况下,我每次调用时都必须检查 getResult 是否返回 null。
  2. 抛出已检查的异常而不是上述异常。
  3. 实例化一个Future&lt;Result&gt;,它在get() 请求上返回空值。我会用 Apache Commons ConcurrentUtils.constantFuture(null) 来做到这一点。在这种情况下,我希望getResult 总是返回一些非空的Future&lt;Result&gt;。我更喜欢这个选项,因为它与第二种情况一致。

在任务执行期间我可能会遇到严重错误,例如内存不足、文件损坏、文件不可用等。

  1. 我想更好的选择在我的情况是返回null,因为任务的结果是一个对象。
  2. 另外,我可以抛出检查异常并在ThreadPoolExecutor.afterExecute 中处理它们(如NiranjanBhat 所建议的那样)。见Handling exceptions from Java ExecutorService tasks

哪种做法更好(在这两种情况下)?

也许有其他方法可以做到这一点,或者我应该使用一种设计模式?

【问题讨论】:

    标签: java future


    【解决方案1】:

    我建议对于任务处理期间的失败,您只需抛出一个适当的异常即可。不要在执行程序中为此添加任何特殊处理。将会发生的是它将被捕获并存储在Future 中。当Futureget 方法被调用时,它会抛出一个ExecutionExceptionget 的调用者可以随后对其进行解包和处理。这本质上就是将正常异常处理转换为Callable/Future 范式的方式。这看起来像这样:

        Future<Result> futureResult = serviceClass.getResult("foo");
    
        try {
            Result result = futureResult.get();
            // do something with result
        }
        catch (ExecutionException ee) {
            Throwable e = ee.getCause();
            // do something with e
        }
    

    鉴于get 的调用者必须对ExecutionExceptions 进行这种处理,您可以利用它来处理提交期间的失败。为此,您可以构造一个类似于 Apache Commons 的 constantFutureFuture,但它会引发给定的异常而不是返回给定的值。我认为 JDK 中没有类似的东西,但写起来很简单(如果乏味的话):

    public class FailedFuture<T> implements Future<T> {
        private final Throwable exception;
    
        public FailedFuture(Throwable exception) {
            this.exception = exception;
        }
    
        @Override
        public T get() throws ExecutionException {
            throw new ExecutionException(exception);
        }
    
        @Override
        public T get(long timeout, TimeUnit unit) throws ExecutionException {
            return get();
        }
    
        @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; }
        @Override public boolean isCancelled() { return false; }
        @Override public boolean isDone() { return true; }
    }
    

    这有点狡猾——你在同步调用的方法中失败了,在异步调用的方法中看起来像是失败了。您正在将处理错误的负担从实际导致错误的代码转移到稍后运行的某些代码上。尽管如此,这确实意味着您可以将所有故障处理代码放在一个地方。这可能足以使这变得有价值。

    【讨论】:

    • 非常感谢您的建议,汤姆。关注它们是有意义的,因为无论如何都需要处理ExecutionExceptions。我其实并不知道。我想,为了避免编写 FailedFuture 的繁琐,我可以将方法的 all 代码推迟到 Callable
    • 你可以这样做;它肯定会让事情变得更简单。不利的一面是,执行者线程必须拿起任务并运行它,纯粹是为了产生一个本可以立即产生的失败指示,这似乎有点浪费。
    【解决方案2】:

    您可以使用afterExecute 方法。这是在 ThreadPoolExecutor 中定义的,您需要覆盖它。
    该方法在每个任务执行完成后调用。您将在此回调方法中获取任务实例。您可以在任务中的某个变量中记录错误并通过此方法访问它。

    【讨论】:

    • 我实际提交任务之前发生的失败怎么办?
    • 你能给我这些错误的例子吗?在提交完成之前,您实际上是在 ExecutorService 之外,并且您可以像上面那样以简单的方式完成它,或者您可以在您的任务代码本身中移动此验证,因此所有事情都发生在一个地方。跨度>
    • 我在问题中添加了示例和更多变体。
    猜你喜欢
    • 1970-01-01
    • 2021-02-03
    • 2015-08-05
    • 2021-11-10
    • 2021-01-06
    • 1970-01-01
    • 1970-01-01
    • 2016-05-09
    • 1970-01-01
    相关资源
    最近更新 更多