简体   繁体   English

如何在主线程上使用 Kotlin 协程 await()

[英]How to use Kotlin coroutines await() on main thread

I just started learning Kotlin coroutines and was trying to simulate some long time API-calls with showing the result on the UI:我刚开始学习 Kotlin 协程,并试图模拟一些长时间的 API 调用并在 UI 上显示结果:

class MainActivity : AppCompatActivity() {
    fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")

    override
    fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        this.setContentView(R.layout.activity_main)
        val resultTV = findViewById(R.id.text) as TextView

        val a = async(CommonPool) {
            delay(1_000L)
            6
        }

        val b = async(CommonPool) {
            delay(1_000L)
            7
        }

        launch(< NEED UI thread here >) {
            val aVal = a.await()
            val bVal = b.await()
            resultTV.setText((aVal * bVal).toString())
        }
    }
}

I don't understand how could I possibly use launch method with main context.我不明白我怎么可能在main上下文中使用launch方法。

Unfortunately, I was not able to find anything about delivering results for some specific threads on the official tutorial for coroutines .不幸的是,我无法在协程的官方教程中找到有关为某些特定线程交付结果的任何信息。

Edit : 编辑

Also see an official example in Kotlin repo 另请参阅Kotlin回购中的官方示例

you need to implement Continuation interface which makes a callback onto Android UI thread and Coroutine context 你需要实现Continuation接口,它可以回调到Android UI线程和Coroutine上下文

eg (from here ) 例如(从这里

private class AndroidContinuation<T>(val cont: Continuation<T>) : Continuation<T> by cont {
    override fun resume(value: T) {
        if (Looper.myLooper() == Looper.getMainLooper()) cont.resume(value)
        else Handler(Looper.getMainLooper()).post { cont.resume(value) }
    }
    override fun resumeWithException(exception: Throwable) {
        if (Looper.myLooper() == Looper.getMainLooper()) cont.resumeWithException(exception)
        else Handler(Looper.getMainLooper()).post { cont.resumeWithException(exception) }
    }
}

object Android : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
        AndroidContinuation(continuation)
}

Then try: 然后尝试:

launch(Android) {
    val aVal = a.await()
    val bVal = b.await()
    resultTV.setText((aVal * bVal).toString()) 
}

more info: 更多信息:

https://medium.com/@macastiblancot/android-coroutines-getting-rid-of-runonuithread-and-callbacks-cleaner-thread-handling-and-more-234c0a9bd8eb#.r2buf5e6h https://medium.com/@macastiblancot/android-coroutines-getting-rid-of-runonuithread-and-callbacks-cleaner-thread-handling-and-more-234c0a9bd8eb#.r2buf5e6h

You shall replace < NEED UI thread here > in your code with UI context from kotlinx-coroutines-android module of kotlinx.coroutines project. 您应该使用来自kotlinx.coroutines项目的kotlinx-coroutines-android模块的UI上下文替换代码中的< NEED UI thread here > Its usage is explained in the Guide to UI programming with coroutines with quite a few examples. 它的用法在使用协同程序的UI编程指南中进行了解释,其中有很多示例。

First of all include right library designed for Android 首先包括为Android设计的正确库

build.gradle 的build.gradle

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android{
...
   dependencies{
      ...
      implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.19.3"

   }

  kotlin {
    experimental {
        coroutines "enable"
    }
  }
}

Then you are free to use UI 然后你可以自由使用UI

suspend private fun getFilteredGList(enumList: List<EnumXXX>) = mList.filter {
    ...
}

private fun filter() {
    val enumList = listOf(EnumX1, EnumX2)
    launch(UI){
        val filteredList = getFilteredList(enumList)
        setMarkersOnMap(filteredList)
    }

}

For those that expose project using kotlin experimental in gradle as .aar or .apk to other projects module - Just remember, when You're using kotlin experimental parent modules/project have to accept kotlin experimental as well 对于那些使用kotlin实验gradle项目暴露为.aar.apk到其他项目模块的人 - 请记住,当你使用kotlin时,实验父模块/项目必须接受kotlin实验以及

kotlin {
    experimental {
        coroutines "enable"
    }
  }

There are a couple of useful tools that can be used for the purpose of long time running API-calls in Activity / Fragment .有几个有用的工具可用于在Activity / Fragment中长时间运行 API 调用。 So basically if you want to run two long running tasks in parallel and update UI after both are finished you can do it the next way:所以基本上如果你想并行运行两个长时间运行的任务并在完成后更新 UI,你可以通过下一种方式进行:

lifecycleScope.launch {
    // launching two tasks in parallel
    val aValDeferred = executeLongRunningTask1Async()
    val bValDeferred = executeLongRunningTask2Async()

    // wait for both of them are finished
    val aVal = aValDeferred.await()
    val bVal = bValDeferred.await()

    // update UI
    resultTV.setText((aVal * bVal).toString())
}

private fun executeLongRunningTask1Async(): Deferred<Int> = lifecycleScope.async(Dispatchers.Default) {
    delay(1_000L)
    6
}

private fun executeLongRunningTask2Async(): Deferred<Int> = lifecycleScope.async(Dispatchers.Default) {
    delay(1_000L)
    7
}

lifecycleScope - is a CoroutineScope , by default it has Dispatchers.Main context, it means we can update UI in the launch block. lifecycleScope - 是一个CoroutineScope ,默认情况下它有Dispatchers.Main上下文,这意味着我们可以在launch块中更新 UI。 For LifecycleScope , use androidx.lifecycle:lifecycle-runtime-ktx:2.4.0 or higher.对于LifecycleScope ,使用androidx.lifecycle:lifecycle-runtime-ktx:2.4.0或更高版本。

lifecycleScope.async(Dispatchers.Default) - here Dispatchers.Default is used as a context of the coroutine to have the async block running in the background thread. lifecycleScope.async(Dispatchers.Default) - 此处Dispatchers.Default用作协程的上下文以使async块在后台线程中运行。

Anko has a wrapper to do it very simply-- see: https://github.com/Kotlin/anko/wiki/Anko-Coroutines Anko有一个非常简单的包装器 - 请参阅: https//github.com/Kotlin/anko/wiki/Anko-Coroutines

private fun doCallAsync() = async(UI) {

    val user = bg { getUser() }
    val name = user.await().name
    val nameView = findViewById(R.id.name) as TextView

    nameView.text = name;

}

This answer may be 2.5yrs after the OP's question but it may still help out others in a similar situation. 在OP的问题之后,这个答案可能是2。5年,但在类似的情况下它可能仍然有助于其他人。

The original goal can be achieved in a far simpler way than the accepted answer above without the use of async/await (statements 1, 2 and 3 will be executed in sequence, with their associated delays behaving as expected): 在不使用async / await的情况下,可以以比上面接受的答案更简单的方式实现原始目标(语句1,2和3将按顺序执行,其相关延迟表现得如预期的那样):

override fun onCreate(savedInstanceState: Bundle?) {
    :
    :
    :
    :
    GlobalScope.launch(Dispatchers.Main) {

    val aVal = a()   // statement 1
    val bVal = b()   // statement 2

    resultTV.setText((aVal * bVal).toString())    // statement 3
    }
    :
    :
}

suspend fun a(): Int {
    delay(1_000L)
    return 6
}

suspend fun b(): Int {
    delay(1_000L)
    return 7
}

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

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