Kotlin挂起函数

16

我有一个 Kotlin 接口

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

我该如何从Java中调用它?这是什么意思?


Continuation <? super List<String>>

enter image description here

1个回答

12

Kotlin使用常规的基于栈的调用约定和“传递续体风格”(CPS)混合实现协程。为此,它通过添加一个隐式参数,在所有suspend fun上执行CPS转换,您可以使用该对象从函数调用的位置继续程序。这就是Kotlin如何设法在函数体内挂起执行的方式:它提取续体对象,将其保存在某个地方,然后使您的函数返回(尚未产生其值)。稍后,它可以通过调用续体对象来实现跳转到函数体中间的效果。

续体基本上是一个回调对象,就像从异步Java API熟悉的那些一样。挂起函数将其结果传递给续体而不是返回其结果。要从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;
    }
};

注意:上述内容仅适用于实验性协程。在最终的 API 中,只有一种恢复方法:resumeWith(result: Result<T>),其中Result是结果类型和internal class Failure的带判别联合,这使它无法从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);
}
否则,您将不得不复制处理函数结果的代码。

1
它在 "return Unconfined.INSTANCE;" 上报错了。它说需要的类型是 CoroutineContext,但提供的是 Unconfined。 - Pravinsingh Waghela
这是一个旧答案,当时协程还是实验性功能。使用发布版本,您无法从Java实现Continuation。 - Marko Topolnik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接