Kotlin:如何在没有运行阻塞的情况下等待来自非挂起的协同路由?
编辑2:我想我误解了文档。我读到: 运行阻塞 此函数不应从协同程序中使用。它设计用于将常规阻塞代码连接到以挂起方式编写的库,以便在Kotlin:如何在没有运行阻塞的情况下等待来自非挂起的协同路由?,kotlin,async-await,kotlin-coroutines,Kotlin,Async Await,Kotlin Coroutines,编辑2:我想我误解了文档。我读到: 运行阻塞 此函数不应从协同程序中使用。它设计用于将常规阻塞代码连接到以挂起方式编写的库,以便在main函数和测试中使用 这意味着除了用于main或测试之外,我根本不应该使用runBlocking()。但我现在意识到我读错了,特别是这一部分: 它旨在将常规阻塞代码与以挂起方式编写的库连接起来 因此,在这个场景中似乎应该使用runBlocking 然而,我认为我应该充分探讨上下文的主题,看看在这种情况下,哪些上下文最适合传递给runBlocking: return
main
函数和测试中使用
这意味着除了用于main
或测试之外,我根本不应该使用runBlocking()
。但我现在意识到我读错了,特别是这一部分:
它旨在将常规阻塞代码与以挂起方式编写的库连接起来
因此,在这个场景中似乎应该使用runBlocking
然而,我认为我应该充分探讨上下文的主题,看看在这种情况下,哪些上下文最适合传递给runBlocking:
return runBlocking(???????) {
job1.await() + job2.await()
}
编辑:很明显,我的问题措辞很糟糕,因为所有回答问题的尝试都忽略了实际问题和我提出的限制。所以让我们尝试另一种方法 这项工作:
fun doSomething(): Int {
val job1 = GlobalScope.async { calculateSomething() }
val job2 = GlobalScope.async { calculateSomething() }
return runBlocking {
job1.await() + job2.await()
}
}
suspend fun calculateSomething(): Int {
delay(1000L)
return 13
}
suspend fun calculateSomethingElse(): Int {
delay(2000L)
return 19
}
我的问题是:我是否可以达到同样的结果:
运行阻塞()
doSomething()
转换为suspend
函数?
来完成以下工作吗
fun doSomething(): Int {
val job1 = GlobalScope.async { calculateSomething() }
val job2 = GlobalScope.async { calculateSomething() }
return ????????? {
job1.?????() + job2.?????()
}
}
suspend fun calculateSomething(): Int {
delay(1000L)
return 13
}
suspend fun calculateSomethingElse(): Int {
delay(2000L)
return 19
}
我有一个小实用程序方法,它运行任何给定的外部命令并返回其输出(即围绕Java Process API的小包装): 此代码工作正常。但是,它会为每个命令执行创建两个附加线程。我想通过切换到协同程序来避免这种情况。我能够做到这一点,但我必须使用
运行阻塞:
class RunningCommand(private val proc: Process) {
fun waitFor(): String {
proc.outputStream.close()
var output = ""
val outputRdr = GlobalScope.async { output = proc.inputStream.bufferedReader().use { it.readText() } }
var error = ""
val errorRdr = GlobalScope.async { error = proc.errorStream.bufferedReader().use { it.readText() } }
proc.waitFor()
runBlocking {
outputRdr.await()
errorRdr.await()
}
if (proc.exitValue() != 0) {
throw RuntimeException("Command returned non-zero status: $output${error}")
}
return output
}
}
这段代码也可以工作,但我知道runBlocking
应该只用于main()
方法和测试,也就是说,不应该以这种方式使用。偷看一下它的实现,它看起来很可怕,而且看起来确实像是人们不想从某个实用方法中重复调用的东西
所以我的问题是:否则我该如何弥合阻塞代码和协同路由之间的鸿沟?或者换句话说,从非挂起
代码等待挂起
功能的正确方法是什么
或者仅仅是因为我的设计是错误的,为了在任何地方使用协程,我需要使main()
方法runBlocking
基本上总是在某个协程范围内?
您可以使用在后台执行操作的调度程序创建自己的作用域。如果要等待某个内容完全完成执行,可以使用withContext
private val myScope = CoroutineScope(Dispatchers.Main)
myScope.launch {
withContext(Dispatchers.IO) {
//to stuff in the background
}
}
运行下面的代码,您将看到它打印20,而不是null
fun main() {
callSuspendFun()
}
suspend fun doWorkAndReturnDouble(num: Int): Int {
delay(1000)
return num * 2
}
fun callSuspendFun() {
val coroutineScope = CoroutineScope(Dispatchers.Main)
coroutineScope.launch {
var num: Int? = null
withContext(Dispatchers.IO) {
val newNum = 10
num = doWorkAndReturnDouble(newNum)
}
println(num)
}
}
因此,要从非挂起函数调用挂起函数,而不使用runBlocking,您必须创建一个协程作用域。在上下文中,您等待代码的执行。您应该使用coroutineScope
suspend fun waitFor(): String = coroutineScope {
proc.outputStream.close()
var output = ""
val outputRdr = async { output = proc.inputStream.bufferedReader().use { it.readText() } }
var error = ""
val errorRdr = async { error = proc.errorStream.bufferedReader().use { it.readText() } }
proc.waitFor()
outputRdr.await()
errorRdr.await()
if (proc.exitValue() != 0) {
throw RuntimeException("Command returned non-zero status: $output${error}")
}
return output
}
您可以将CoroutineScope()
与Dispathers.IO
一起使用,它将在后台线程中启动一个协程,并将在该线程上卸载您的执行
class RunningCommand(private val proc: Process) {
fun waitFor(): String {
// Starting a new coroutine on Background thread (IO)
proc.outputStream.close()
var output = ""
CoroutineScope(Dispatchers.Unconfined).async {
val outputRdr = async { output = proc.inputStream.bufferedReader().use { it.readText() } }
var error = ""
val errorRdr = async { error = proc.errorStream.bufferedReader().use { it.readText() } }
proc.waitFor()
// No need of runBlocking
// await() call would wait for completion of its async block
outputRdr.await() // Execution would block until outputRdr block completion on IO thread
// At this stage, outputRdr block is completed
errorRdr.await() // Execution would block until errorRdr block completion on IO thread
// At this stage, errorRdr block is completed
if (proc.exitValue() != 0) {
throw RuntimeException("Command returned non-zero status: $output${error}")
}
return@async output
}
return output
}
}
注意:如果您从任何协程上下文调用waitFor()
方法,您可以通过编写coroutineScope{}
而不是coroutineScope(Dispatchers.IO)来在同一协程上下文上继续工作。启动{}
,正确管理结构化并发 对于任何未来的旅行者,如果他们犯了与我相同的错误-runBlocking
不仅可以在main
/tests中使用,还可以:
它的设计目的是将常规阻塞代码连接到以挂起方式编写的库
不知何故,我给人的印象是,仅用于某些库函数是有害的,但事实并非如此。我不确定我是否遵循了-您能否更改示例,以显示如何在不使用runBlocking
的情况下等待非挂起函数的协程或挂起函数?我添加了一个您可以自己运行的示例。希望能有更多帮助。我想你还没有完全理解我的问题。我需要阻塞/非挂起代码来阻塞并等待协同路由完成,然后得到它的结果。如果我理解正确的话,您在这里所做的是演示如何从非挂起代码以“触发并忘记”的方式启动协同程序……这意味着waitFor()
现在是一种suspend
方法waitFor()
是从非suspend
方法调用的。如何使调用方等待waitFor()
的结果?此代码不编译waitFor()
现在不返回任何内容。此外,proc.outputStream.close()
和proc.waitFor()
的行现在生成一个警告“不适当的阻塞方法调用”。此外,不接受return语句,因为启动块的类型为CoroutineScope。(->Unit
。我已更正错误proc.outputStream.close()
发出了阻塞调用的警告,所以我将其放在协同路由范围之外和之上。我还处理了返回值错误,如果您希望从waitFor()
获得正确的输出
,则需要将此函数挂起。当前,输出
值将与正确的值不匹配,因为返回输出
行未等待启动的协同程序completion@AmirAbiri:我已经编辑了代码以处理该情况。现在,将返回具有正确值的输出。另外,waitFor()
是非挂起函数。请运行此代码。这也不起作用-waitFor()
只会立即返回”
。还请注意,您的原始示例在全局范围内有两个async()
,存在一个协程问题
class RunningCommand(private val proc: Process) {
fun waitFor(): String {
// Starting a new coroutine on Background thread (IO)
proc.outputStream.close()
var output = ""
CoroutineScope(Dispatchers.Unconfined).async {
val outputRdr = async { output = proc.inputStream.bufferedReader().use { it.readText() } }
var error = ""
val errorRdr = async { error = proc.errorStream.bufferedReader().use { it.readText() } }
proc.waitFor()
// No need of runBlocking
// await() call would wait for completion of its async block
outputRdr.await() // Execution would block until outputRdr block completion on IO thread
// At this stage, outputRdr block is completed
errorRdr.await() // Execution would block until errorRdr block completion on IO thread
// At this stage, errorRdr block is completed
if (proc.exitValue() != 0) {
throw RuntimeException("Command returned non-zero status: $output${error}")
}
return@async output
}
return output
}
}