Kotlin 暂停乐趣

标签 kotlin kotlin-coroutines

我有 Kotlin 界面

interface FileSystem {
    suspend fun getName(path: Path): List<String>
}

如何从 Java 调用它?什么是

Continuation <? super List<String>>

enter image description here

最佳答案

Kotlin 使用常规的基于堆栈的调用约定和连续传递样式 (CPS) 来实现协同程序。为此,它对所有 suspend fun 执行 CPS 转换 s 通过添加一个隐式参数,一个可以用来从调用函数的地方继续程序的对象。这就是 Kotlin 设法在函数体内暂停执行的技巧:它提取延续对象,将其保存在某处,然后让函数返回(尚未产生其值)。以后可以通过调用continuation对象来实现跳转到你的函数体中间的效果。

延续基本上是一个回调对象,就像那些熟悉的异步 Java API 一样。可挂起函数不是返回其结果,而是将其结果传递给延续。调用suspend fun从 java 中,您必须创建这样的回调。这是一个例子:

Continuation<List<String>> myCont = new Continuation<List<String>>() {
    @Override public void resume(List<String> result) {
        System.out.println("Result of getName is " + result);
    }
    @Override public void resumeWithException(Throwable throwable) {
        throwable.printStackTrace();
    }
    @NotNull @Override public CoroutineContext getContext() {
        return Unconfined.INSTANCE;
    }
};

NOTE: The above works only with experimental coroutines. In the final API there's just one resumption method: resumeWith(result: Result<T>) where Result is a discriminated union of the result type and the internal class Failure, which makes it inaccessible from Java.

让我们也创建一个 FileSystem 的模拟实现。界面:

class MockFs : FileSystem {
    override suspend fun getName(path: Path): List<String> {
        suspendCoroutine<Unit> {
            println("getName suspended")
        }
        println("getName resumed")
        return listOf("usr", "opt")
    }
}

现在我们可以从 Java 中调用它了:

Object result = new MockFs().getName(Paths.get(""), myCont);
System.out.println("getName returned " + result);

打印出来

getName suspended
getName returned
kotlin.coroutines.experimental.intrinsics.CoroutineSuspendedMarker@6ce253f1

getName()返回一个特殊的标记对象,表示函数已暂停。一旦恢复,该函数会将其实际结果传递给我们的回调。

现在让我们改进MockFs所以我们可以访问延续:

class MockFs : FileSystem {
    var continuation : Continuation<Unit>? = null

    override suspend fun getName(path: Path): List<String> {
        suspendCoroutine<Unit> {
            continuation = it
            println("getName suspended")
        }
        println("getName resumed")
        return listOf("usr", "opt")
    }
}

现在我们可以手动恢复继续。我们可以使用这段代码:

MockFs mockFs = new MockFs();
mockFs.getName(Paths.get(""), myCont);
mockFs.getContinuation().resume(Unit.INSTANCE);

这将打印出来

getName suspended
getName resumed
Result of getName is [usr, opt]

在现实生活中,可挂起函数将使用某种机制在结果可用时恢复自身。例如,如果它是一些异步 API 调用的包装器,它将注册一个回调。当异步 API 调用回调时,它会依次调用我们的延续。您不需要像我们在模拟代码中那样手动恢复它。

一个 suspend fun还可以选择直接返回其结果。例如,使用此 MockFs代码

class MockFs : FileSystem {
    override suspend fun getName(path: Path) = listOf("usr", "opt") 
}

在Java中我们可以说

System.out.println(new MockFs().getName(Paths.get(""), myCont));

它会打印出 [usr, opt] .我们甚至可以传入 Continuation 的空实现。 .

最苛刻的情况发生在您事先不知道函数是否会自行挂起时。在这种情况下,一个好的方法是在调用站点编写以下内容:

Object retVal = mockFs.getName(Paths.get(""), myCont);
if (retVal != IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
    myCont.resume((List<String>) retVal);
}

否则您将不得不复制处理函数结果的代码。

关于Kotlin 暂停乐趣,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51808992/

相关文章:

kotlin - 为什么我的 tornadoFX ObservableList 没有收到更新?

kotlin - 库函数是否应该挂起或返回延迟

android - 在 Compose 中使用 DeepLink 会导致无法向后导航

Kotlin中双数的字符串插值

kotlin - 编写自定义kotlin-dsl时如何忽略显式导入诸如gradle kotlin脚本之类的类

android - 使用 kotlin 协程处理 retrofit 2.6 的无互联网连接错误

kotlin - IDE 对 Dispatchers.IO 协程中的阻塞调用发出警告

Kotlin - 带循环的协程

android - Kotlin 协程 : why is this not throwing a NetworkOnMainThreadException?

android - Kotlin:不同 fragment 之间的通信