延迟Kotlin buildSequence的推荐方法是什么?

时间:2018-03-13 17:44:36

标签: kotlin kotlinx.coroutines

我正在尝试轮询分页API并在用户出现时向他们提供新项目。

fun connect(): Sequence<T> = buildSequence {
    while (true) {
        // result is a List<T>
        val result = dataSource.getFirstPage()
        yieldAll(/* the new data in `result` */)

        // Block the thread for a little bit
    }
}

以下是示例用法:

for (item in connect()) {
    // do something as each item is made available
}

我的第一个想法是使用delay函数,但我收到了这条消息:

  

受限制的暂停函数只能在其受限制的协程范围内调用成员或扩展暂停函数

这是buildSequence的签名:

public fun <T> buildSequence(builderAction: suspend SequenceBuilder<T>.() -> Unit): Sequence<T>

我认为此消息意味着我只能使用SequenceBuilder中的suspend函数:yieldyieldAll,并且不允许使用任意suspend函数调用。

现在,每次轮询API时,我都会使用它来阻止序列构建一秒钟:

val resumeTime = System.nanoTime() + TimeUnit.SECONDS.toNanos(1)
while (resumeTime > System.nanoTime()) {
    // do nothing
}

这有效,但它似乎不是一个好的解决方案。以前有人遇到过这个问题吗?

1 个答案:

答案 0 :(得分:7)

为什么不起作用?一些研究

当我们查看buildSequence时,我们可以看到它需要builderAction: suspend SequenceBuilder<T>.() -> Unit作为其参数。作为该方法的客户端,您将能够提供suspend lambda,其SequenceBuilder作为其接收者(阅读lambda with receiver here)。
SequenceBuilder本身已注明RestrictSuspension

@RestrictsSuspension
@SinceKotlin("1.1")
public abstract class SequenceBuilder<in T> ...

注释的定义和评论如下:

/**
 * Classes and interfaces marked with this annotation are restricted
 * when used as receivers for extension `suspend` functions. 
 * These `suspend` extensions can only invoke other member or extension     
 * `suspend` functions on this particular receiver only 
 * and are restricted from calling arbitrary suspension functions.
 */
@SinceKotlin("1.1") @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY)
public annotation class RestrictsSuspension

正如RestrictSuspension文档所述,在buildSequence的情况下,您可以传递一个lambda,SequenceBuilder作为接收者但是有限制的可能性,因为你只能在这个特定的接收器上调用“其他成员或扩展suspend功能”。这意味着,传递给buildSequence的广告块可能会调用SequenceBuilder上定义的任何方法(例如yieldyieldAll)。另一方面,由于块“被限制调用任意暂停函数”,因此使用delay不起作用。生成的编译器错误会验证它:

  

受限制的挂起函数只能在其受限制的协程范围内调用成员或扩展暂停函数。

最终,您需要知道buildSequence创建了一个协程,它是同步协程的一个示例。在您的示例中,序列代码将通过调用connect()在消耗序列的同一线程中执行。

如何延迟序列?

据我们了解,buildSequence创建了同步序列。在这里使用常规线程阻塞很好:

fun connect(): Sequence<T> = buildSequence {
    while (true) {
        val result = dataSource.getFirstPage()
        yieldAll(result)
        Thread.sleep(1000)
    }
}

但是,你真的想要整个线程被阻止吗?或者,您可以按照here所述实现异步序列。因此,使用delay和其他暂停功能将有效。