我使用Future
和Callable<>
创建了一些异步任务。
问题:如果在异步执行期间发生Exception
,我需要访问用于构建可调用对象的参数。但是如何?
示例:
List<Callable<Response> tasks = new ArrayList<>();
taks.add(() -> sendRequest(req));
futures = executor.invokeAll(tasks);
for (future : futures) {
try {
Response rsp = future.get();
} catch (ExecutionException e) {
//problem: I need to access req.getType() value of the req Object here. But How?
}
}
赞:我想从req.getType()
收集所有请求值,因此我知道哪些异步请求失败。并将错误消息返回给用户。
答案 0 :(得分:6)
根据Javadoc,每个符合ExecutorService
的人都会:
@return表示任务的Futures列表 由迭代器产生的顺序 给定的任务列表,每个都已完成
因此,考虑到您在开始时有一个任务列表,您在列表中的索引Future
处遇到的失败的每个i
对象将对应于索引处的Callable
任务任务列表中的i
:
List<Request> requests = // obtain request
List<Callable<Response>> tasks = new ArrayList<>();
requests.forEach(req -> tasks.add(() -> sendRequest(req)));
List<Future<Response>> futures = executor.invokeAll(tasks);
for (int i = 0; i < futures.size(); i++) {
try {
Response rsp = future.get();
handleResponse(rsp);
} catch (ExecutionException e) {
Request req = requests.get(i); // this is request parameter for send request at index i, assuming your Service does not violate its contract
handleRequestFailure(req, e.getCause());
}
}
答案 1 :(得分:2)
请注意:ExecutionException
您正在抓取包裹围绕Callable
实施所引发的异常。
因此,如果您希望在该异常中包含详细信息 - 那么,您必须确保抛出和包装的此异常包含所需信息。
或者,您必须实际跟踪哪个Future属于&#34;输入参数&#34;。就像创建{<1}} 填充,而将Callable对象推送到执行程序中一样。
答案 2 :(得分:1)
虽然问题已得到彻底解答,但我想提出一些其他想法,或许可以说明一些已经讨论过的问题。
提交要在不同线程中运行的一组任务的一个有趣方面是,我们无法再控制这些任务的完成顺序。某些任务可能先运行,或者甚至那些先运行的任务可能需要比其他任务更长的时间才能完成。
现在考虑一下:
List<Future<Integer>> futures = new ArrayList<>();
futures.add(executor.submit(() -> doTaskWith(100));
futures.add(executor.submit(() -> doTaskWith(200));
for(future: futures) {
future.get(); //Uh oh, blocking here!
}
在上面的示例中,如果我们的第一个未来需要30秒才能完成,而第二个将只需要15秒;然后我们实际上阻止了30秒开始处理结果,但第一个结果是在15秒之前完成的。
重点是,如果我们可以在结果可用时立即开始汇总,我们就可以改进。
解决此类问题的一种方法是使用ExecutorCompletionService提交任务并控制结果。
例如:
ExecutorService executor = Executors.newFixedThreadPool(3);
ExecutorCompletionService<Double> completionService = new ExecutorCompletionService<>(executor);
completionService.submit(() -> doTaskWith(100));
completionService.submit(() -> doTaskWith(200));
Future<Double> future = completionService.take(); //whichever finishes first
ExecutorCompletionService
就像一个队列一样,一旦任务完成,无论该任务是什么,我们都可以poll或take将其排除在队列之外并获得其结果。
想象一下,我们有一个函数来计算整数的平方根:
static double sqrt(int n) {
if(n > 0) {
return Math.sqrt(n);
}
throw new IllegalArgumentException("Invalid integer: " + n);
}
所以,现在想象一下,我们想提交一堆整数来计算它们的平方根。
例如:
Map<Future<Double>, Integer> tasks = Stream.of(4, 25, 36, 49, -5)
.collect(toMap(n -> completionService.submit(() -> sqrt(n)),
Function.identity()));
因此,我们从流中获取每个整数,并从每个整数中创建Callable<Integer>
(即() -> sqrt(n)
)。每个callable都获取上下文整数n
的平方根。然后我们将那个callable传递给我们的完成服务并获得它的未来(即completionService.submit(callable)
)。然后我们返回future
地图的关键字,以及整数n
地图中该关键字的值,这是我们在返回的对象中看到的(即Map<Future<Double>, Integer> tasks
)
现在我们有一组期货(来自我们提交的任务)作为关键字,以及用于获取这些期货的整数作为价值。
现在我们可以做到:
Set<Future<Double>> futures = new HashSet<>(tasks.keySet());
Set<Future<Double>> failures = new LinkedHashSet<>();
while (!futures.isEmpty()) {
try {
Future<Double> future = completionService.take();
int n = tasks.get(future); //original value of n
double squareRoot = future.get(); //result of task
System.out.printf("The square root of %d is %f%n", n, squareRoot);
}
catch (ExecutionException e) {
Integer n = tasks.get(future); //original value of n
System.err.printf("Failure to obtain square root of %d: %s%n", n, e.getMessage());
failures.add(future);
}
catch (InterruptedException e) {
//TODO: handle interruption somehow, perhaps logging partial results?
}
finally {
futures.remove(future);
}
}
if(!failures.isEmpty()) {
//TODO you may want to do do something about the failures you got
}
由于未来是我们地图中的关键,当completionService
报告某个特定的未来已经准备就绪时,我们可以轻松地在地图中查找未来,并获得{{1}的原始值我们提交进行处理(即n
)。
解决问题的另一种方法是确保您抛出的异常包含恢复原始请求对象所需的所有上下文详细信息。
例如,我们可以按如下方式更改int n = tasks.get(future)
代码:
sqrt
现在,如果我们无法计算平方根,我们将返回包含无法处理的原始值的static double sqrt(int n) {
if(n > 0) {
return Math.sqrt(n);
}
throw new InvalidIntegerException(n);
}
static class InvalidIntegerException extends RuntimeException {
private final Integer n;
InvalidIntegerException(int n) {
super("Invalid integer: " + n);
this.n = n;
}
public Integer getInteger() {
return n;
}
}
。这个例外可以让我们回到原来的价值。
原始异常将由执行程序服务包装在InvalidIntegerException
中。
所以,现在我们可以这样做:
ExecutionException
}
我更喜欢第一种解决方案,因为我不必对抛出的异常类型做任何假设,这使得代码更易于维护。也许其他人可以评论每种策略的利弊。