簡體   English   中英

如何在主線程上使用 Kotlin 協程 await()

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

我剛開始學習 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())
        }
    }
}

我不明白我怎么可能在main上下文中使用launch方法。

不幸的是,我無法在協程的官方教程中找到有關為某些特定線程交付結果的任何信息。

編輯

另請參閱Kotlin回購中的官方示例

你需要實現Continuation接口,它可以回調到Android UI線程和Coroutine上下文

例如(從這里

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)
}

然后嘗試:

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

更多信息:

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

您應該使用來自kotlinx.coroutines項目的kotlinx-coroutines-android模塊的UI上下文替換代碼中的< NEED UI thread here > 它的用法在使用協同程序的UI編程指南中進行了解釋,其中有很多示例。

首先包括為Android設計的正確庫

的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"
    }
  }
}

然后你可以自由使用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)
    }

}

對於那些使用kotlin實驗gradle項目暴露為.aar.apk到其他項目模塊的人 - 請記住,當你使用kotlin時,實驗父模塊/項目必須接受kotlin實驗以及

kotlin {
    experimental {
        coroutines "enable"
    }
  }

有幾個有用的工具可用於在Activity / Fragment中長時間運行 API 調用。 所以基本上如果你想並行運行兩個長時間運行的任務並在完成后更新 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 - 是一個CoroutineScope ,默認情況下它有Dispatchers.Main上下文,這意味着我們可以在launch塊中更新 UI。 對於LifecycleScope ,使用androidx.lifecycle:lifecycle-runtime-ktx:2.4.0或更高版本。

lifecycleScope.async(Dispatchers.Default) - 此處Dispatchers.Default用作協程的上下文以使async塊在后台線程中運行。

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;

}

在OP的問題之后,這個答案可能是2。5年,但在類似的情況下它可能仍然有助於其他人。

在不使用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