Groovy 的 future.get() 返回 null

huangapple go评论52阅读模式
英文:

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 &lt;T&gt; Future&lt;T&gt; StartAsyncTask(Closure&lt;T&gt; onTask) { 
		return this.GetThreadPool()
			.submit(onTask);
	}
	
	public static &lt;T&gt; T AwaitResult(Future&lt;T&gt; 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 &quot;hello&quot;;
			},
			{
				Thread.sleep(50);
				return &quot;world&quot;;
			},
		].collect { Closure onTask -&gt; return AsyncUtils.StartAsyncTask(onTask) }
		
		promises.each { promise -&gt; 
			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()  // ==&gt; prints world
println c.run()   // ==&gt; 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 &lt;T&gt; Future&lt;T&gt; StartAsyncTask(Closure&lt;T&gt; onTask) { 
        return this.GetThreadPool()
            .submit((Callable)onTask);  // &lt;&lt;== the only effective change
    }
    
    public static &lt;T&gt; T AwaitResult(Future&lt;T&gt; 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 &quot;world&quot;
}

assert AsyncUtils.AwaitResult(future)=='world'
英文:

groovy Closure implements both - Runnable and Callable.

def c={&#39;world&#39;}

println c.call()  // ==&gt; prints world
println c.run()   // ==&gt; 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 &lt;T&gt; Future&lt;T&gt; StartAsyncTask(Closure&lt;T&gt; onTask) { 
        return this.GetThreadPool()
            .submit((Callable)onTask);  // &lt;&lt;== the only effective change
    }
    
    public static &lt;T&gt; T AwaitResult(Future&lt;T&gt; 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 &quot;world&quot;
}

assert AsyncUtils.AwaitResult(future)==&#39;world&#39;

答案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 Futures 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 Futures 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 &lt;T&gt; Future&lt;T&gt; StartAsyncTask(Closure&lt;T&gt; onTask) { 
		return CompletableFuture.supplyAsync(onTask)
	}
}

and then my test case, understanding (and simulating) the problem a bit more, to:

@Test
	void testStartAsyncTask() { 
		final List&lt;CompletableFuture&gt; promises = [
           {
        	   Thread.sleep(100);
        	   return &quot;hello&quot;;
           },
           {
        	   Thread.sleep(50);
        	   return &quot;world&quot;;
           },
       ].collect { Closure onTask -&gt; return AsyncUtils.StartAsyncTask(onTask) }

	   promises.eachWithIndex { promise, int idx -&gt; 
		   promise.thenApplyAsync { String str -&gt;
			   if (idx == 0) { 
				   assert str.equals(&#39;world&#39;)
				   return
			   }
			   assert str.equals(&#39;hello&#39;)
		   }
	   }
	}

and all is well!

huangapple
  • 本文由 发表于 2023年2月24日 14:28:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/75553240.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定