简体   繁体   English

Kotlin:如何在没有 runBlocking 的情况下等待非挂起的协程?

[英]Kotlin: How to wait for a coroutine from non-suspend without runBlocking?

Edit 2: I think I misunderstood the documentation.编辑 2:我想我误解了文档。 I read:我读:

runBlocking运行阻塞

This function should not be used from a coroutine.不应在协程中使用此函数。 It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in main functions and in tests.它旨在将常规阻塞代码桥接到以挂起风格编写的库,以用于main功能和测试。

To mean that I shouldn't use runBlocking() at all other than for main or tests.意思是我不应该使用runBlocking()除了main或测试之外。 But I now realise I read it wrong, specifically this part:但我现在意识到我读错了,特别是这部分:

It is designed to bridge regular blocking code to libraries that are written in suspending style它旨在将常规阻塞代码桥接到以挂起样式编写的库

So it seems that runBlocking should be used in this scenario.所以在这个场景中似乎应该使用runBlocking。

However I think I should fully grok the topic of contexts to see which contexts is best suited to pass to runBlocking in this case:但是,我认为我应该完全理解上下文的主题,以查看在这种情况下哪些上下文最适合传递给 runBlocking:

return runBlocking(???????) {
    job1.await() + job2.await()
}

Edit: Clearly I phrased the question badly as all attempts to answer it miss the actual question and the constraint I am posing.编辑:很明显,我对这个问题的措辞很糟糕,因为所有回答它的尝试都错过了我提出的实际问题和约束。 So let's try a different approach...所以让我们尝试不同的方法......

This works:这有效:

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
}

My question is: is there anyway I can achieve the same result:我的问题是:无论如何我可以达到相同的结果:

  1. Without using runBlocking() at all. runBlocking()使用runBlocking()
  2. Without turning doSomething() into a suspend function.无需doSomething()转换为suspend函数。

? ?

In other words: is there anything I can put instead of ??????换句话说:有什么我可以代替?????? to make the following work?使以下工作?

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
}

I have a small utility method that runs any given external command and returns its output (ie small wrapper around Java Process API): 我有一个小的实用程序方法,它运行任何给定的外部命令并返回其输出(即围绕 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 } }
 
 
 

This code works correctly. 此代码正常工作。 However it creates two additional threads for each command execution. 但是,它为每个命令执行创建了两个额外的线程。 I wanted to avoid that by switching to coroutines. 我想通过切换到协程来避免这种情况。 I was able to do this, but I had to use runBlocking : 我能够做到这一点,但我不得不使用 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 } }
 
 
 

This code also works, but I read that runBlocking should only be used on main() methods and tests, ie not meant to be used in this way. 这段代码也有效,但我读到 runBlocking应该只用于 main()方法和测试,即不打算以这种方式使用。 Peeking at its implementation it looks horrible and indeed looks like something one wouldn't want to call repeatedly from some utility method. 看看它的实现,它看起来很可怕,而且确实看起来像是人们不想从某个实用程序方法重复调用的东西。

So my question is: how else am I supposed to bridge the gap between blocking code and coroutines? 所以我的问题是:我还应该如何弥合阻塞代码和协程之间的差距? Or put differently, what is the correct way to wait for a suspend function from non- suspend code? 或者换句话说,从非 suspend代码等待 suspend函数的正确方法是什么?

Or is it simply that my design is wrong, and to use Coroutines anywhere down the line I need to make the main() method runBlocking and essentially always be inside some coroutine scope? 或者仅仅是我的设计是错误的,并且要在任何地方使用协程,我需要使 main()方法 runBlocking并且基本上总是在某个协程范围内?

For any future travellers who make the same mistake as me - runBlocking is OK to use not just in main / tests - but also:对于任何与我犯同样错误的未来旅行者 - runBlocking不仅可以在main / 测试中使用 - 而且还可以:

It is designed to bridge regular blocking code to libraries that are written in suspending style它旨在将常规阻塞代码桥接到以挂起样式编写的库

Somehow I got the impression that it is evil to use for just some library function, but it is not.不知何故,我的印象是,仅用于某些库函数是邪恶的,但事实并非如此。

You can create your own scope with dispatchers that do operations in the background.您可以使用在后台执行操作的调度程序创建自己的范围。 You can use withContext if you want to wait for something to completely finish executing.如果您想等待某件事完全执行完毕,您可以使用 withContext。

private val myScope = CoroutineScope(Dispatchers.Main) 

myScope.launch {

   withContext(Dispatchers.IO) {
        //to stuff in the background
    }

}

You run the code bellow, you'll see that it prints 20, not null.你运行下面的代码,你会看到它打印了 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)
    }
}

So to call a suspend function from a non-suspend function, without using runBlocking, you have to create a coroutine scope.因此,要从非挂起函数调用挂起函数,而不使用 runBlocking,您必须创建一个协程作用域。 And withContext you wait for the execution of the code.和 withContext 你等待代码的执行。

You should use coroutineScope你应该使用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
}

You can use CoroutineScope() with Dispathers.IO which will launch a coroutine in background thread and would offload your execution on that thread您可以将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
}

}

Note : If you're calling waitFor() method from any coroutine context, you can carry on work on same coroutine context by writing coroutineScope { } instead of CoroutineScope(Dispatchers.IO).launch { } , managing structured concurrency properly.注意:如果您从任何协程上下文调用waitFor()方法,您可以通过编写coroutineScope { }而不是CoroutineScope(Dispatchers.IO).launch { }来在相同的协程上下文上继续工作,正确管理结构化并发。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM