英文:
Groovy future.get() returns null
问题
我正在处理一个Groovy项目,用于合并表格,但在使用我的异步工具时遇到了一个问题。我将问题缩小到了这一点。
这是AsyncUtils
:
package com.signaturemd.sposZohoMergeScript.utils
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.Future
public final class AsyncUtils {
private static ExecutorService threadPool;
public static <T> Future<T> StartAsyncTask(Closure<T> onTask) {
return this.GetThreadPool()
.submit(onTask);
}
public static <T> T AwaitResult(Future<T> future) {
return future.get();
}
public static ExecutorService GetThreadPool() {
if (!this.threadPool)
this.threadPool = Executors.newCachedThreadPool();
return this.threadPool;
}
}
为了尝试解决它,我编写了一个JUnit测试用例,模拟了在实际项目代码库中使用的情况:
package com.signaturemd.sposZohoMergeScript.utils;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class AsyncUtilsTest {
@Test
void testAwaitResult() {
final List promises = [
{
Thread.sleep(100);
return "hello";
},
{
Thread.sleep(50);
return "world";
},
].collect { Closure onTask -> return AsyncUtils.StartAsyncTask(onTask) }
promises.each { promise ->
final String result = AsyncUtils.AwaitResult(promise);
assert result != null;
}
}
}
断言失败了,因为在等待结果后,result
在某种程度上变成了null
...
这是什么原因导致了这种情况,这是否意味着我应该等待所有承诺(Futures)解决后再开始工作单元(在我的情况下是合并)?
免责声明:我使用的是Java 17。
英文:
I am working on a Groovy project to merge Sheets, but faced an issue over my async utils. I narrowed it down to that.
Here is the AsyncUtils
:
package com.signaturemd.sposZohoMergeScript.utils
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.Future
public final class AsyncUtils {
private static ExecutorService threadPool;
public static <T> Future<T> StartAsyncTask(Closure<T> onTask) {
return this.GetThreadPool()
.submit(onTask);
}
public static <T> T AwaitResult(Future<T> future) {
return future.get();
}
public static ExecutorService GetThreadPool() {
if (!this.threadPool)
this.threadPool = Executors.newCachedThreadPool();
return this.threadPool;
}
}
To try to resolve it, I write JUnit test case simulating my use case in the real project codebase:
package com.signaturemd.sposZohoMergeScript.utils;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class AsyncUtilsTest {
@Test
void testAwaitResult() {
final List promises = [
{
Thread.sleep(100);
return "hello";
},
{
Thread.sleep(50);
return "world";
},
].collect { Closure onTask -> return AsyncUtils.StartAsyncTask(onTask) }
promises.each { promise ->
final String result = AsyncUtils.AwaitResult(promise);
assert result != null;
}
}
}
The assertion is failing, as result
is somehow null
after awaiting the result...
What is causing this to happen, and does this mean I should wait on all promises (Futures) to resolve before starting unit of work (in my case merging)?
DISCLAIMER: I'm on Java 17
答案1
得分: 1
以下是翻译的内容:
def c={'world'}
println c.call() // ==> prints world
println c.run() // ==> prints null
当你调用 ExecutorService.submit(Closure)
时,编译器会选择闭包的 Runnable
接口。
将 Closure
转换为 Callable
可以解决这个问题。
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.Future
import java.util.concurrent.Callable
@groovy.transform.CompileStatic
public final class AsyncUtils {
private static ExecutorService threadPool;
public static <T> Future<T> StartAsyncTask(Closure<T> onTask) {
return this.GetThreadPool()
.submit((Callable)onTask); // <<== the only effective change
}
public static <T> T AwaitResult(Future<T> future) {
return future.get();
}
public static ExecutorService GetThreadPool() {
if (!this.threadPool)
this.threadPool = Executors.newCachedThreadPool();
return this.threadPool;
}
}
def future = AsyncUtils.StartAsyncTask{
Thread.sleep(1000)
return "world"
}
assert AsyncUtils.AwaitResult(future)=='world'
英文:
groovy Closure implements both - Runnable and Callable.
def c={'world'}
println c.call() // ==> prints world
println c.run() // ==> prints null
when you are calling ExecutorService.submit(Closure)
compiler chooses Runnable interface of the closure.
Casting Closure
to Callable
solves the issue
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.Future
import java.util.concurrent.Callable
@groovy.transform.CompileStatic
public final class AsyncUtils {
private static ExecutorService threadPool;
public static <T> Future<T> StartAsyncTask(Closure<T> onTask) {
return this.GetThreadPool()
.submit((Callable)onTask); // <<== the only effective change
}
public static <T> T AwaitResult(Future<T> future) {
return future.get();
}
public static ExecutorService GetThreadPool() {
if (!this.threadPool)
this.threadPool = Executors.newCachedThreadPool();
return this.threadPool;
}
}
def future = AsyncUtils.StartAsyncTask{
Thread.sleep(1000)
return "world"
}
assert AsyncUtils.AwaitResult(future)=='world'
答案2
得分: 0
以下是您要翻译的部分:
"Turns out that I didn't need to create new thread pool or the equivalent of JS's async
/await
keywords. Just treat these Future
s as the thennables...
- I don't need thread pool explicitly
- I don't need the
async
/await
equivalent. It wouldn't work efficiently anyways. We want the first thennable that resolves, not to resolve them all in the order they're supplied.
I just change my util class to :
public final class AsyncUtils {
public static <T> Future<T> StartAsyncTask(Closure<T> onTask) {
return CompletableFuture.supplyAsync(onTask);
}
}
and then my test case, understanding (and simulating) the problem a bit more, to:
@Test
void testStartAsyncTask() {
final List<CompletableFuture> promises = [
{
Thread.sleep(100);
return "hello";
},
{
Thread.sleep(50);
return "world";
},
].collect { Closure onTask -> return AsyncUtils.StartAsyncTask(onTask); }
promises.eachWithIndex { promise, int idx ->
promise.thenApplyAsync { String str ->
if (idx == 0) {
assert str.equals('world');
return;
}
assert str.equals('hello');
}
}
}
and all is well!"
英文:
Turns out that I didn't need to create new thread pool or the equivalent of JS's async
/await
keywords. Just treat these Future
s as the thennables...
- I don't need thread pool explicitly
- I don't need the
async
/await
equivalent. It wouldn't work efficiently anyways. We want the first thennable that resolves, not to resolve them all in the order they're supplied.
I just change my util class to :
public final class AsyncUtils {
public static <T> Future<T> StartAsyncTask(Closure<T> onTask) {
return CompletableFuture.supplyAsync(onTask)
}
}
and then my test case, understanding (and simulating) the problem a bit more, to:
@Test
void testStartAsyncTask() {
final List<CompletableFuture> promises = [
{
Thread.sleep(100);
return "hello";
},
{
Thread.sleep(50);
return "world";
},
].collect { Closure onTask -> return AsyncUtils.StartAsyncTask(onTask) }
promises.eachWithIndex { promise, int idx ->
promise.thenApplyAsync { String str ->
if (idx == 0) {
assert str.equals('world')
return
}
assert str.equals('hello')
}
}
}
and all is well!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论