英文:
For loop wait for all CompletableFutures to give result, and add result to int
问题
我有一个方法getCount(id),它从数据库中返回一个整数,类型为CompletableFuture<Integer>。我想遍历所有的id,获取它们的结果,并将这些结果累加到一个全局整数中。然后我想对这个整数做一些操作:
```java
int totalCount;
for (String id : API.getIDs()) {
// OtherAPI.getCount(id)将返回CompletableFuture<Integer>
OtherAPI.getCount(id).thenAccept(count -> totalCount += count);
}
System.out.println("" + totalCount);
这会导致错误,因为在thenAccept中不能将一个整数添加到另一个整数,因为它是一个消费者。有什么方法可以解决这个问题?
<details>
<summary>英文:</summary>
I have a method getCount(id) that returns an integer from a database, as a CompletableFuture<Integer>. I want to loop through all the ids, and get the result for all of them, and add them to one global int. Then I want to do something with that int:
```java
int totalCount;
for (String id : API.getIDs()) {
//OtherAPI.getCount(id) will return the CompletableFuture<Integer>
OtherAPI.getCount(id).thenAccept(count -> totalCount += count);
}
System.out.println("" + totalCount);
This gives an error, because you can't add an integer to another integer in thenAccept, because it's a consumer. What is the way to do this?
答案1
得分: 2
请注意,传递给 thenAccept
的函数可以由任意线程执行,所以 Java 不允许它们修改局部变量,这是一件 幸运的 事情。当您将变量更改为堆上的字段时,编译器会接受它,但结果会完全破坏。
最简单的解决方案是
int totalCount = 0;
for(String id: API.getIDs()) {
totalCount += OtherAPI.getCount(id).join();
}
它只是等待每个值的可用性,然后在本地对它们求和。根据 OtherAPI.getCount(…)
调用封装的操作,这甚至可能是最有效的解决方案。
但是,当这些操作需要显着时间并且确实可以真正并行运行,即在内部不依赖于共享资源时,等待所有操作开始之前可能是有益的。
您可以这样做
CompletableFuture<Integer> result = CompletableFuture.completedFuture(0);
for(String id: API.getIDs()) {
result = result.thenCombine(OtherAPI.getCount(id), Integer::sum);
}
System.out.println(result.join());
在这里,循环完全无需等待。它将安排求和操作,在两个操作完成时执行。然后,只有最后的 join
调用会等待最终结果。此时,所有异步操作已经被提交。
英文:
Mind that the function passed to thenAccept
can be performed by an arbitrary thread, so thankfully, Java does not allow them to modify a local variable. When you change the variable to a field on the heap, the compiler would accept it, but the result would be entirely broken.
The simplest solution would be
int totalCount = 0;
for(String id: API.getIDs()) {
totalCount += OtherAPI.getCount(id).join();
}
It just waits for the availability of each value, to sum them locally. Depending on the operations encapsulated by the OtherAPI.getCount(…)
calls, this might even be the most efficient solution.
But when these operations take a significant time and can truly run in parallel, i.e. do not depend on a shared resource internally, it might be beneficial not to wait before all operations have been commenced.
You can do this like
CompletableFuture<Integer> result = CompletableFuture.completedFuture(0);
for(String id: API.getIDs()) {
result = result.thenCombine(OtherAPI.getCount(id), Integer::sum);
}
System.out.println(result.join());
Here, the loop is entirely wait-free. It will schedule the summing operation, to be done when two operations have been completed. Then, only the join
call at the end will wait for the final result. At this point, all asynchronous operations have been submitted already.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论