CoroutineScopes如何工作?
假设我有一个
enum class ConceptualPosition{
INVALID,
A,B
}
假设我有一个用户可以从其中单击A
或B
的UI。
我现在想要一个Actor接收用户输入,但是忽略它,直到实际请求输入为止。为了简单起见,我们假设只有一种方法请求职位。
sealed class PositionRequest{
/**report the next position offered*/
object ForwardNext:PositionRequest()
}
所以我们可以构造如下:
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)
}
}
}
}
}
然后构建一个外观与之交互:
class BasicUI{
private val dispatcher = Dispatchers.IO
/*start a Position Actor that receives input from the UI and forwards them on demand*/
private val requests = Channel<PositionRequest>()
private val offeredPositions = Channel<ConceptualPosition>()
private val nextPosition = Channel<ConceptualPosition>()
init {
runBlocking(dispatcher){
positionActor(offeredPositions,requests,nextPosition)
}
}
/** Receives a [ConceptualPosition] that may or may not get accepted and acted upon.*/
fun offerPosition(conceptualPosition: ConceptualPosition) = runBlocking(dispatcher) {
offeredPositions.send(conceptualPosition)
}
/** waits for a [ConceptualPosition] to be offered via [offerPosition], then accepts it*/
fun getPosition(): ConceptualPosition = runBlocking(dispatcher){
requests.send(PositionRequest.ForwardNext)
nextPosition.receive()
}
}
这当然不起作用,因为runBlocking
是CoroutineScope
,所以init
不会返回,直到positionActor(offeredPositions,requests,nextPosition)
发起的协程结束...永远不会因为里面有while(true)
。
那么,如果让BasicUI
实现CoroutineScope
怎么办?毕竟,是 what Roman Elizarov said we should do at the KotlinConf,如果我正确理解他的话,应该将positionActor(...)
创建的协程绑定到BasicUI
实例,而不是{{ 1}}块。
让我们看看...
runBlocking
让我们建立一个小的测试用例:我给演员提供一些import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlin.coroutines.CoroutineContext
class BasicUI:CoroutineScope{
private val dispatcher = Dispatchers.IO
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job
/*start a Position Actor that receives input from the UI and forwards them on demand*/
private val requests = Channel<PositionRequest>()
private val offeredPositions = Channel<ConceptualPosition>()
private val nextPosition = Channel<ConceptualPosition>()
init {
positionActor(offeredPositions,requests,nextPosition)
}
/** Receives a [ConceptualPosition] that may or may not get accepted and acted upon.*/
fun offerPosition(conceptualPosition: ConceptualPosition) = runBlocking(dispatcher) {
offeredPositions.send(conceptualPosition)
}
/** waits for a [ConceptualPosition] to be offered via [offerPosition], then accepts it*/
fun getPosition(): ConceptualPosition = runBlocking(dispatcher){
requests.send(PositionRequest.ForwardNext)
nextPosition.receive()
}
}
,他应该忽略它们,然后启动一个协程,持续提供A
,其中一个将返回给我。当我要求演员担任职位时。
B
这将导致
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'")
}
...什么也没有。
显然,actor engaged
ACTOR: entering while loop
ACTOR: offeredPosition.onReceive(A)
ACTOR: offeredPosition.onReceive(A)
ACTOR: offeredPosition.onReceive(A)
ACTOR: offeredPosition.onReceive(A)
offered some 'A's
ACTOR: offeredPosition.onReceive(A)
requesting a position
ACTOR: requests.onReceive(PositionRequest$ForwardNext@558da0e9)
从未提供过-因此也从未转发过-这导致主线程被阻塞(在这种情况下应该如此)。
我扔了一个
B
进入if(conceptualPosition == ConceptualPosition.B) throw RuntimeException("B offered?!")
,也没有例外,所以...
在这一点上,我可能不得不承认我还不了解Kotlin BasicUI.offerPosition
。
为什么这个例子不起作用?
答案 0 :(得分:2)
这里似乎有两个问题:
offerPosition
/ getPosition
不是挂起函数。在大多数情况下,使用runBlocking
是错误的解决方案,在必须与同步代码或主要功能进行接口连接时应使用该方法。async
在当前的CoroutineScope
中执行。对于您的主要功能,这是runBlocking
。该文档实际上描述了行为:事件循环的内部实现中此构建器的默认CoroutineDispatcher,该事件循环处理此阻塞线程中的继续操作,直到完成此协程。有关kotlinx.coroutines提供的其他实现,请参见CoroutineDispatcher。
简单来说,async
块将不会轮到在事件循环中执行,而其他延续正在使用它。由于getPosition
正在阻止,因此您会阻止事件循环。
将阻塞函数替换为暂停函数,并用withContext(dispatcher)
来分派到其他执行程序上,将允许异步函数运行并最终解决状态。