我有一个小的实用程序方法,可以运行任何给定的外部命令并返回其输出(即Java Process API的小型包装器):
class RunningCommand(private val proc: Process) {
fun waitFor(): String {
proc.outputStream.close()
var output = ""
val outputRdr = thread { output = proc.inputStream.bufferedReader().use { it.readText() } }
var error = ""
val errorRdr = thread { error = proc.errorStream.bufferedReader().use { it.readText() } }
proc.waitFor()
outputRdr.join()
errorRdr.join()
if (proc.exitValue() != 0) {
throw RuntimeException("Command returned non-zero status: $output$error")
}
return output
}
}
此代码正常工作。但是,它为每个命令执行创建了两个附加线程。我想通过改用协程避免这种情况。我能够做到这一点,但是我不得不使用runBlocking
:
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()
方法和测试中使用,即不应以这种方式使用。偷看它的实现看起来很可怕,而且确实看起来像是不想从某个实用程序方法中反复调用的东西。
所以我的问题是:我还应该如何弥合阻止代码和协程之间的差距?或者换句话说,从非suspend
代码中等待suspend
函数的正确方法是什么?
还是仅仅是我的设计是错误的,并且要在任何地方使用协程,我都需要制作main()
方法runBlocking
,并且基本上总是在某个协程范围内?
答案 0 :(得分:0)
对于以后犯任何与我相同的错误的旅行者-rubBlocking
不仅可以在main
/测试中使用-还可以:
它旨在将常规的阻止代码桥接到以挂起样式编写的库中
我以某种方式给人的印象是,仅将某些库函数使用是邪恶的,但事实并非如此。
答案 1 :(得分:-1)
您可以使用在后台执行操作的调度程序创建自己的作用域。如果要等待某些事情完全完成执行,可以使用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,必须创建一个协程作用域。有了withContext,您就可以等待代码的执行。
答案 2 :(得分:-1)
您应该使用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
}
答案 3 :(得分:-1)
您可以将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).launch { }
来在相同的协程上下文上进行工作,并正确管理结构化并发。