Asynchronous Kotlin-协同程序范围,为什么不';我的异步命令不能执行吗?

Asynchronous Kotlin-协同程序范围,为什么不';我的异步命令不能执行吗?,asynchronous,kotlin,actor,channel,kotlinx.coroutines,Asynchronous,Kotlin,Actor,Channel,Kotlinx.coroutines,合作计划是如何运作的 假设我有一个 enum class ConceptualPosition{ INVALID, A,B } 假设我有一个用户界面,用户可以从中单击任意位置,a或B 我现在想要一个参与者,它接收用户输入,但在实际请求输入之前忽略它。为了简单起见,假设只有一种方式可以申请职位 sealed class PositionRequest{ /**report the next position offered*/ object ForwardNext

合作计划是如何运作的

假设我有一个

enum class ConceptualPosition{
    INVALID,
    A,B
}
假设我有一个用户界面,用户可以从中单击任意位置,
a
B

我现在想要一个参与者,它接收用户输入,但在实际请求输入之前忽略它。为了简单起见,假设只有一种方式可以申请职位

sealed class PositionRequest{
    /**report the next position offered*/
    object ForwardNext:PositionRequest()
}
import ConceptualPosition.*
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking

fun main(args: Array<String>) = runBlocking{
    val ui = BasicUI()
    println("actor engaged")

    //these should all be ignored
    repeat(5){ui.offerPosition(A)}
    println("offered some 'A's")

    //keep offering 'B' so that eventually, one will be offered after we request a position
    async { while(true){ui.offerPosition(B)} }

    //now get a 'B'
    println("requesting a position")
    val pos = ui.getPosition()
    println("received '$pos'")
}
所以我们可以这样构造:

fun CoroutineScope.positionActor(
        offeredPosition:ReceiveChannel<ConceptualPosition>,
        requests:ReceiveChannel<PositionRequest>,
        output:SendChannel<ConceptualPosition>
) = launch{
    var lastReceivedPosition = INVALID
    var forwardNextReceived = 0

    println("ACTOR: entering while loop")
    while(true) {
        select<Unit> {
            requests.onReceive {
                println("ACTOR: requests.onReceive($it)")
                when (it) {
                    is PositionRequest.ForwardNext -> ++forwardNextReceived
                }
            }

            offeredPosition.onReceive {
                println("ACTOR: offeredPosition.onReceive($it)")
                lastReceivedPosition = it
                if (forwardNextReceived > 0) {
                    --forwardNextReceived
                    output.send(it)
                }
            }
        }
    }
}
让我们构建一个小测试用例:我将为演员提供一些他应该忽略的
a
s,然后启动一个持续提供
B
s的协同程序,当我向演员请求职位时,其中一个将返回给我

sealed class PositionRequest{
    /**report the next position offered*/
    object ForwardNext:PositionRequest()
}
import ConceptualPosition.*
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking

fun main(args: Array<String>) = runBlocking{
    val ui = BasicUI()
    println("actor engaged")

    //these should all be ignored
    repeat(5){ui.offerPosition(A)}
    println("offered some 'A's")

    //keep offering 'B' so that eventually, one will be offered after we request a position
    async { while(true){ui.offerPosition(B)} }

    //now get a 'B'
    println("requesting a position")
    val pos = ui.getPosition()
    println("received '$pos'")
}
。。。什么也没有

显然,
B
从来没有被提供过,因此也从来没有被转发过,这会导致主线程阻塞(在这种情况下应该是这样的)

我扔了一个硬币

if(conceptualPosition == ConceptualPosition.B) throw RuntimeException("B offered?!")
进入
BasicUI.offerPosition
,也没有例外,所以

在这一点上,我可能不得不承认我还不了解Kotlin
CoroutineScope


为什么这个例子不起作用?

这里似乎有两个问题:

  • offerPosition
    /
    getPosition
    不是挂起函数。在大多数情况下,使用
    runBlocking
    是错误的解决方案,在必须与同步代码或主功能接口时应使用
  • 在当前
    CoroutineScope
    中执行不带任何参数的
    async
    。对于您的主要功能,这是
    运行阻塞
    。文档实际上描述了以下行为:
  • 在事件循环的内部实现中,此生成器的默认协程分配器,该事件循环处理此阻塞线程中的连续性,直到完成此协程。有关kotlinx.coroutines提供的其他实现,请参见CoroutineDispatcher

    简单地说,
    async
    块在其他continuation使用它时不会在事件循环中执行。因为
    getPosition
    正在阻止事件循环


    将阻塞函数替换为挂起函数,并使用上下文(dispatcher)在不同的执行器上进行调度,将允许异步函数运行并最终解决状态。

    这里似乎有两个问题:

  • offerPosition
    /
    getPosition
    不是挂起函数。在大多数情况下,使用
    runBlocking
    是错误的解决方案,在必须与同步代码或主功能接口时应使用
  • 在当前
    CoroutineScope
    中执行不带任何参数的
    async
    。对于您的主要功能,这是
    运行阻塞
    。文档实际上描述了以下行为:
  • 在事件循环的内部实现中,此生成器的默认协程分配器,该事件循环处理此阻塞线程中的连续性,直到完成此协程。有关kotlinx.coroutines提供的其他实现,请参见CoroutineDispatcher

    简单地说,
    async
    块在其他continuation使用它时不会在事件循环中执行。因为
    getPosition
    正在阻止事件循环


    将阻塞函数替换为挂起函数,并使用上下文(dispatcher)在不同的执行器上进行调度,将允许异步函数运行并最终解决状态。

    是否有一种解决方案可以避免将
    offerPosition
    /
    getPosition
    挂起?我的意思是,拥有门面的全部意义在于,客户不需要知道涉及到协同程序;从他的观点来看,他只是调用一个返回
    概念位置的方法。通过阻塞同步返回值的函数不会与co例程混合使用。您需要使它们
    挂起
    ,或者返回一个未来而不是一个值(
    延迟
    或类似的内容)。在当前状态下,您根本无法从使用co例程中获益。是否有一种解决方案可以避免将
    offerPosition
    /
    getPosition
    挂起?我的意思是,拥有门面的全部意义在于,客户不需要知道涉及到协同程序;从他的观点来看,他只是调用一个返回
    概念位置的方法。通过阻塞同步返回值的函数不会与co例程混合使用。您需要使它们
    挂起
    ,或者返回一个未来而不是一个值(
    延迟
    或类似的内容)。在当前状态下,您根本无法从使用co例程中获益。
    if(conceptualPosition == ConceptualPosition.B) throw RuntimeException("B offered?!")