7

如果我想并行执行资源密集型代码, 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. 如果params提供无效,则getResult立即返回 null。在这种情况下,我getResult每次调用它时都必须检查是否返回 null。
  2. 抛出已检查的异常而不是上述异常。
  3. 实例化根据请求Future<Result>返回 null 的a。get()我会用 Apache Commons 做到这一点ConcurrentUtils.constantFuture(null)。在这种情况下,我希望getResult总是返回一些 non-null Future<Result>。我更喜欢这个选项,因为它与第二种情况一致。

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

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

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

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

4

2 回答 2

11

我建议对于任务处理过程中的失败,你只需抛出一个适当的异常。不要在执行程序中为此添加任何特殊处理。将会发生的是它将被捕获并存储在Future. 当Future'get方法被调用时,它会抛出一个ExecutionException,然后调用者get可以对其进行解包和处理。这本质上就是将普通异常处理转换为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 进行这种处理,您可以利用它来处理提交期间的失败。为此,您可以构造一个Future类似于 Apache Commons 的constantFuture,但它会抛出一个给定的异常而不是返回一个给定的值。我认为 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; }
}

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

于 2012-12-18T11:14:06.183 回答
2

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

于 2012-12-18T08:38:21.770 回答