简体   繁体   English

如何等待协程结束

[英]How to wait for end of a coroutine

I have some code below.我在下面有一些代码。 Delay (3000) is just replacement for a long loop (or cycle).延迟(3000)只是长循环(或循环)的替代。 I'm expecting that after completion of loop println(res) will print “Some String” and then enable button .我期待循环完成后println(res)将打印“Some String”,然后启用button But in real life println(res) prints an empty string and button became enabled at same time when I click it.但在现实生活中, println(res)打印一个空字符串,并且当我单击它时button同时启用。 My question is: how I can wait for end of a coroutine and only after completion of the coroutine run println(res) and button.isEnabled = true .我的问题是:如何等待协程结束,并且只有在协程完成后才运行println(res)button.isEnabled = true

private var res: String = ""

private suspend fun test(): String {
    delay(3000) // delay - just replacement for long loop
    return "Some String" // String received after loop
}

fun onClick(view: View) {
    res = ""
    button.isEnabled = false
    GlobalScope.launch {
        res = withContext(Dispatchers.Default) {
            test()
        }
    }
    println(res) // 1. trying to get string received after loop, but not working
    button.isEnabled = true // 2. button must be enabled after loop in cycle, but it's not waiting till end of loop
}

The main thing to understand here is that code within coroutine is by default executed sequentially.这里要理解的主要事情是协程中的代码默认是按顺序执行的。 Ie coroutine is executed asynchronously in relation to "sibling" code, but code within coroutine executes synchronously by default.即协程相对于“兄弟”代码异步执行,但默认情况下协程内的代码同步执行。

For example:例如:

fun DoSometing () { 

coroutineA {
doSomethingA1()
doSomethingA2()
}

some additional code 

}

Corroutine A will execute async in relation to some additional code but doSometingA2 will be executed after doSomethingA1 is done. Corroutine A 将执行与一些附加代码相关的异步操作,但 doSometingA2 将在 doSomethingA1 完成后执行。

That means, that within a coroutine every next piece of code will be executed after the previous one is done.这意味着,在协程中,下一段代码将在前一段代码完成后执行。 So, whatever you want to execute when coroutine is done, you just put at the end of that coroutine and declare context ( withContext ) in which you want to execute it.因此,无论您想在协程完成后执行什么,您只需将其放在该协程的末尾并声明要在其中执行它的上下文( withContext )。

The exception is of course if you start another async piece of code within coroutine (like another coroutine).当然,如果您在协程中启动另一段异步代码(如另一个协程),则例外。

EDIT: If you need to update UI from the coroutine, you should execute that on the main context, ie you'll have something like this:编辑:如果您需要从协程更新 UI,您应该在主上下文中执行它,即您将拥有如下内容:

GlobalScope.launch (Dispatchers.IO) {

   //do some background work
   ...
   withContext (Dispatchers.Main) { 
       //update the UI 
       button.isEnabled=true  
       ...
     }
}

You can try some thing like this:你可以尝试这样的事情:

suspend fun saveInDb() {
    val value = GlobalScope.async {
       delay(1000)
       println("thread running on [${Thread.currentThread().name}]")
       10
    }
    println("value =  ${value.await()} thread running on [${Thread.currentThread().name}]")
} 

await will wait for the coroutine to finish and then run code below it await 将等待协程完成,然后运行它下面的代码

fun onClick(view: View) {
    res = ""
    button.isEnabled = false
    GlobalScope.launch(Dispatchers.Main){ // launches coroutine in main thread
         updateUi()
    }
}

suspend fun updateUi(){
    val value = GlobalScope.async { // creates worker thread
       res = withContext(Dispatchers.Default) {
          test()
       }
    }
    println(value.await()) //waits for workerthread to finish
    button.isEnabled = true //runs on ui thread as calling function is on Dispatchers.main
}

why you don't move println and button.isEnabled inside GlobalScope.launch coroutine.为什么你不在GlobalScope.launch协程中移动printlnbutton.isEnabled

fun onClick(view: View) {
    res = ""
    button.isEnabled = false
    GlobalScope.launch {
        val res = withContext(Dispatchers.Default) {
            test()
        }

        println(res)
        button.isEnabled = true 
    }
}

if you whant your code run on main thread add Dispatchers.Main as an argument.如果您希望您的代码在main线程上运行,请添加Dispatchers.Main作为参数。

GlobalScope.launch(Dispatchers.Main) {
   val res = withContext(Dispatchers.Default) {
            test()
        }

        println(res)
        button.isEnabled = true 
}

now println and button.isEnabled run on main thread and test() fun runs on Default which in real is a worker thread.现在printlnbutton.isEnabledmain线程上运行,而test() fun 在Default上运行,它实际上是一个工作线程。

Use the Job.join(): Unit method to wait for a coroutine to finish before continuing with current the thread:使用Job.join(): Unit方法在继续当前线程之前等待协程完成:

//launch coroutine
var result = ""
val request = launch {
    delay(500)
    result = "Hello, world!"
}

//join coroutine with current thread
request.join()

//print "Hello, world!"
println(result)

launch is for situations where you don't care about the result outside the coroutine. launch适用于您不关心协程之外的结果的情况。 To retrieve the result of a coroutine use async .要检索协程的结果,请使用async

val res = GlobalScope.async(Dispatchers.Default) { test() }.await()

Note: avoid using GlobalScope , provide your own CoroutineScope instead.注意:避免使用GlobalScope ,而是提供您自己的CoroutineScope

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

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