【问题标题】:How to properly deal with exceptions coming from ListenableFuture guava?如何正确处理来自 ListenableFuture guava 的异常?
【发布时间】:2015-06-02 21:10:36
【问题描述】:

我有一个库,其中我为我们的客户提供了两种方法,同步和异步。他们可以调用他们认为适合他们目的的任何方法。

  • executeSynchronous() - 等到我有结果,然后返回结果。
  • executeAsynchronous() - 立即返回一个 Future,如果需要,可以在其他事情完成后进行处理。

他们将传递其中包含用户 ID 的 DataKey 对象。我们将根据用户 ID 确定调用哪台机器。因此,我们将使用 AsyncRestTemplate 对 url 进行 http 调用,然后根据是否成功将响应发送回给他们。

下面是我的界面:

public interface Client {
    // for synchronous
    public DataResponse executeSync(final DataKey key);

    // for asynchronous
    public Future<DataResponse> executeAsync(final DataKey key);
}

下面是我的实现:

public class DataClient implements IClient {

    // does this have to be final?
    private final AsyncRestTemplate restTemplate = new AsyncRestTemplate();

    @Override
    public DataResponse executeSync(final DataKey keys) {
        Future<DataResponse> responseFuture = executeAsync(keys);
        DataResponse response = null;
        try {
            response = responseFuture.get(keys.getTimeout(), TimeUnit.Milliseconds);
        } catch (CancellationException e) {
            // what to do here?
        }  catch (InterruptedException e) {
            // is this right way to deal with InterruptedException?
            throw new RuntimeException("Interrupted", e);
        } catch (ExecutionException e) {
            // what do you mean by ExecutionException? And how should we deal with this?
            DataLogging.logErrors(e.getCause(), DataErrorEnum.ERROR_CLIENT, keys);
            response = new DataResponse(null, DataErrorEnum.ERROR_CLIENT, DataStatusEnum.ERROR);
        } catch (TimeoutException e) {
            DataLogging.logErrors(e.getCause(), DataErrorEnum.TIMEOUT_ON_CLIENT, keys);
            response = new DataResponse(null, DataErrorEnum.TIMEOUT_ON_CLIENT, DataStatusEnum.ERROR);       
        }

        return response;
    }

    @Override
    public Future<DataResponse> executeAsync(final DataKey keys) {
        final SettableFuture<DataResponse> responseFuture = SettableFuture.create();
        restTemplate.exchange(createURL(keys), HttpMethod.GET, keys.getEntity(), String.class).addCallback(
                new ListenableFutureCallback<ResponseEntity<String>>() {
                    @Override
                    public void onSuccess(ResponseEntity<String> result) {
                        responseFuture.set(new DataResponse(result.getBody(), DataErrorEnum.OK,
                                DataStatusEnum.SUCCESS));
                    }

                    @Override
                    public void onFailure(Throwable ex) {
                        DataLogging.logErrors(ex, DataErrorEnum.ERROR_SERVER, keys);
                        responseFuture.set(new DataResponse(null, DataErrorEnum.ERROR_CLIENT,
                                DataStatusEnum.ERROR));
                    }
                });

        return responseFuture;

    }
}

现在我的问题是:

  • 如何正确处理executeSync的catch块中的异常? CancellationException 和 TimeoutException 有什么区别吗?还有我们应该如何处理ExecutionException
  • 我的 DataKey 在我的界面中是否必须是最终的?如果我在 executeAsync 实现中删除最终变量,则会收到编译错误 Cannot refer to a non-final variable keys inside an inner class defined in a different method
  • 这是在我的executeAsync 方法中使用 ListenableFutureCallback 的正确方法吗?或者有没有更好的使用方法?

对于我的同步和异步实现的设计,也欢迎任何输入/建议。

【问题讨论】:

    标签: java multithreading asynchronous guava future


    【解决方案1】:

    我假设您使用的是 Spring 4 (AsyncRestTemplate)。在这种情况下,您获得的 ListenableFuture 并不是真正的 Guava 的 ListenableFuture,而是在 Spring 中的克隆。无论如何,您应该像处理标准 Future 中的异常一样处理异常。

    回答您的问题:

    // does this have to be final? private final AsyncRestTemplate
    restTemplate = new AsyncRestTemplate();
    

    它没有(在这种情况下),但这是一个很好的做法,因为通常它可以减少对象的可变性,从而简化对其行为的推理。

    catch (CancellationException e) {
        // what to do here?
    }
    

    如果任务被取消(通过 Future#cancel 或 ExecutorService#shutdownNow),将抛出 CancellationException。它不会发生在您的情况下,因为只有您引用了 Future 和(通过私有 AsyncRestTemplate 隐式)执行查询使用的 ExecutorService。所以

    throw new AssertionError("executeAsync task couldn't be cancelled", e);
    

    CancellationException 和 超时异常?

    在 Future#get 调用中,您已指定超时。如果在 keys.getTimeout() 毫秒后结果仍然不可用,将抛出 TimeoutException。

    catch (InterruptedException e) {
       // is this right way to deal with InterruptedException?
       throw new RuntimeException("Interrupted", e);
    }
    

    在这种情况下没有。当 client 的 线程被中断时会抛出 InterruptedException。您不拥有该线程,因此您应该传播 InterruptedException(即声明 executeSync(DataKey keys) throws InterruptedException)。如果由于某种原因您无法更改方法的签名,那么至少在抛出 RuntimeException 之前恢复中断标志 (Thread.currentThread().interrupt())。

    catch (ExecutionException e) {
       // what do you mean by ExecutionException? And how should we deal with this?
       DataLogging.logErrors(e.getCause(), DataErrorEnum.ERROR_CLIENT, keys);
       response = new DataResponse(null, DataErrorEnum.ERROR_CLIENT, DataStatusEnum.ERROR);
    }
    

    ExecutionException 表示作为 Callable/Runnable 提交给 ExecutorService 的代码在执行过程中抛出了异常。在您的情况下,永远不会抛出 ExecutionException ,因为您返回 SettableFuture 并在 onSuccess 和 onFailure 回调中设置了值,因此您可以在 catch 块中抛出 AssertionError 。 ExecutionException 没有通用的响应策略。

    我的 DataKey 在我的界面中是否必须是最终的?

    它在executeAsync实现中必须是final,因为你从匿名类(onFailure回调)中引用它;

    这是在我的 executeAsync 方法中使用 ListenableFutureCallback 的正确方法吗?或者有没有更好的使用方法?

    看不出有什么问题。

    一些建议:

    1. 考虑为异步客户端配置线程池。

    默认情况下 AsyncRestTemplate 使用SimpleAsyncTaskExecutor 为每个请求创建新线程。这可能并不适合您的所有客户。请注意,如果您遵循此建议,对 CancellationException 的响应必须不同,因为客户端现在可以引用 ExecutorService:抛出 RuntimeException 应该没问题。

    1. 在 (java)doc 中描述默认使用的线程池!

    2. 我会拆分同步和异步版本。

    3. 我认为使用同步 RestTemplate 并通过同步版本实现异步版本会简化实现。

    4. 考虑返回更灵活的 ListenableFuture 而不是普通的 Future(使用 SettableListenableFuture 而不是 SettableFuture)。

    【讨论】:

    • 感谢您的建议。感谢你的帮助。我的大部分问题都有这个想法。如果我需要为异步客户端配置线程池,我将如何做到这一点,正如您在第一个建议中所说的那样?另外,您能否提供一个 SettableListenableFuture 的示例。这将有助于我更好地理解?为什么你建议使用 SettableListenableFuture 而不是 SettableFuture?
    • 使异步客户端可配置的最简单方法可能是使用 ThreadPoolTask​​Executor 参数创建第二个构造函数。在此构造函数中,只需创建 AsyncRestTemplate,将传递的线程池作为 AsyncTaskExecutor 提供。
    • executeAsync 返回标准的 Future。如果将 SettableFuture 替换为 SettableListenableFuture,则可以返回 LinstenableFuture。这将允许 executeAsync 的调用者添加回调(类似于您正在执行的操作)。
    • 实际上在你的情况下我错了 ExecutionException 。查看更新。
    • 当然知道了。你能告诉我为什么你建议为异步客户端配置线程池吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-29
    相关资源
    最近更新 更多