You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Groovy中future.get()返回null问题排查与咨询

Groovy异步工具类Future.get()返回null的问题分析

问题场景

开发表格合并的Groovy项目时,AsyncUtils工具类中future.get()始终返回null,JUnit测试用例断言失败。工具类及测试代码如下:

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;
        }
    }

}

问题原因

核心问题是Groovy闭包被错误识别为Runnable而非Callable:

  • ExecutorService的submit方法有两个重载:一个接受Runnable(无返回值),一个接受Callable<T>(有返回值)。
  • 直接传递Groovy闭包时,若未显式指定类型,Groovy会默认适配为Runnable,此时run()方法无返回值,导致Future.get()返回null。
  • 工具类虽声明了Closure<T>,但提交时未显式转换为Callable<T>,JVM自动选用了Runnable重载。

解决方案

修改StartAsyncTask方法,显式将闭包转换为Callable<T>,确保调用接受Callable的submit重载:

public static <T> Future<T> StartAsyncTask(Closure<T> onTask) { 
    return this.GetThreadPool()
        .submit(onTask as Callable<T>);
}

Groovy支持通过as关键字将Closure转换为Callable接口,这样闭包的返回值会被正确传递,Future.get()就能拿到预期结果。

关于等待所有Futures的问题

当前测试中逐个调用AwaitResult(即future.get())的方式可行,因为get()是阻塞式方法,会等待当前Future执行完成后再处理下一个。如果合并工作需要所有异步任务结果就绪才能执行,确实需要等待所有Futures完成——你可以保持当前逐个等待的方式,也可以用ExecutorService.invokeAll()一次性提交所有任务并等待全部完成,核心是先解决闭包的Callable适配问题。

内容的提问来源于stack exchange,提问作者Mike Warren

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 23:57:21