簡體   English   中英

我可以反射性地從 Java 調用一個 Kotlin 協程並將其包裝在 CompletableFuture 中嗎?

[英]Can I invoke a Kotlin coroutine from Java reflectively and wrap it in CompletableFuture?

我正在反射性地訪問 Kotlin class 並嘗試調用協程,將其包裝在CompletableFuture中。 查看來自 Spring 的示例,他們設法使用kotlinx-coroutines-reactor將這樣的反射調用包裝在Mono中:

KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method);
Mono<Object> mono = MonoKt.mono(Dispatchers.getUnconfined(), (scope, continuation) ->
                        KCallables.callSuspend(function, getSuspendedFunctionArgs(target, args), continuation));

我可以輕松地調用Mono#toFuture() ,但我試圖避免拖入對 Reactor 的依賴。 我可以在不通過Mono的情況下實現同樣的目標嗎?

查看kotlinx-coroutines-jdk8 ,似乎存在與上述類似的東西,但我不知道如何使用它。 也許:

CompletableFuture<Object> future = FutureKt.future(
         GlobalScope.INSTANCE, Dispatchers.getUnconfined(), CoroutineStart.DEFAULT, (scope, continuation) ->
                KCallables.callSuspend(function, getSuspendedFunctionArgs(target, args), continuation));

但這只是盲目猜測,因為我有 next to 0 Kotlin 的知識。 CoroutineStart.DEFAULTGlobalScope.INSTANCE在這里還可以嗎?

順便說一句, CoroutineContext是如何在需要 CoroutineContext 的地方傳遞CoroutineDispatcher ( Dispatchers.getUnconfined() ) 的? 我應該改用EmptyCoroutineContext嗎?

首先,可掛起代碼不容易從 Java 調用,這是設計使然。 我們可以做到,但正如您已經注意到的那樣,這並不簡單,需要一些有關協程的內部知識。

通常,創建一個適配層將可暫停代碼轉換為 Java 可以理解的內容是個好主意:使用future() 的CompletableFuture或使用runBlocking()的阻塞代碼。 在 Kotlin 本身中編寫它更容易,因此即使您使用 Java 中的 Kotlin 庫並且該庫不為期貨提供此類適配器,您也可以考慮在消費者端添加一個非常薄的 Kotlin 包裝器來進行翻譯。

如果這不可能,我們仍然可以使用 Java 中的相同future()實用程序,您已經這樣做了。 這種方法有點笨拙,因為我們需要為所有可選參數提供值。

future()通常很容易使用。 我們需要提供一個 lambda,它將調用我們的可暫停 function,傳遞我們在 lambda 中收到的延續。就是這樣。 然而,有多個參數來控制過程,它們使事情復雜化。 此外,如果我們不想,我們不必使用ReflectJvmMapping.getKotlinFunction()KCallables.callSuspend()getSuspendedFunctionArgs() 同樣,我們需要做的就是在 lambda 中調用 function,我們可以“Java 方式”執行此操作。

簡單示例:

object Database {
    @JvmStatic
    suspend fun loadUsername(userId: Int): String {
        delay(5000)
        return "User$userId"
    }
}
// Note the method receives additional `Continuation` parameter - it is always the last param.
Method method = Database.class.getMethod("loadUsername", int.class, Continuation.class);
CompletableFuture<String> future = FutureKt.future(
        GlobalScope.INSTANCE, EmptyCoroutineContext.INSTANCE, CoroutineStart.DEFAULT, (scope, continuation) -> {
            try {
                // We invoke the method passing its own arguments and the continuation.
                return method.invoke(null, 5, continuation);
            } catch (IllegalAccessException | InvocationTargetException e) {
                throw new RuntimeException(e);
            }
        });

System.out.println("Launched, waiting...");
var result = future.get();
// Result: User5
System.out.println("Result: " + result);

我們這里不必使用 Java 反射,我們可以直接調用Database.loadUsername(5, continuation) ,但問題是專門針對反射的。

關於參數的幾句話:

  • GlobalScope.INSTANCE - 這與結構化並發的概念有關,使用GlobalScope意味着后台操作沒有任何“所有者”來控制它。 在 Java 中,我們沒有結構化並發,因此在大多數情況下,只需使用GlobalScope就可以了。
  • EmptyCoroutineContext.INSTANCE - 我認為使用EmptyCoroutineContext比使用Dispatchers.getUnconfined()更安全。 不同之處在於,無限制調度程序在啟動或恢復它的線程內運行協程; 本例中的空上下文使用了一個特殊的全局線程池。 Unconfined 的性能更好,但它是一個更高級的功能,只有當我們知道可掛起的代碼並且可以確定使用 unconfined 是安全的時,我們才應該使用它。 對於通用適配層來說聽起來不太好。 我不確定 Spring 團隊決定使用 unconfined 的原因是什么,也許他們有更具體的用例。

順便說一句,我還觀察到Dispatchers.getUnconfined()不能用作協程上下文,但我無法解釋原因。 似乎是 IntelliJ 中的某種錯誤(?)。 CoroutineDispatcher擴展了AbstractCoroutineContextElement實現CoroutineContext.Element擴展CoroutineContext 因此, CoroutineDispatcherCoroutineContext的子類型。 事實上,我們可以編譯這段代碼並且它運行得很好,即使 IntelliJ 說它是錯誤的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM