如何在主线程上使用 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
方法。
不幸的是,我无法在 the official tutorial for coroutines.
上找到有关交付某些特定线程结果的任何信息
编辑:
另见 an official example in Kotlin repo
你需要实施Continuation interface which makes a callback onto Android UI thread and Coroutine context
例如(来自 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)
}
然后尝试:
launch(Android) {
val aVal = a.await()
val bVal = b.await()
resultTV.setText((aVal * bVal).toString())
}
更多信息:
您应将代码中的 < NEED UI thread here >
替换为 kotlinx.coroutines project. Its usage is explained in the Guide to UI programming with coroutines 的 kotlinx-coroutines-android
模块中的 UI
上下文,并提供大量示例。
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;
}
首先包含为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)
}
}
对于那些在 gradle
中使用 kotlin experimental 公开项目的人 .aar 或 .apk 到其他项目模块 - 请记住,当您使用 kotlin experimental parent modules/project 必须接受 kotlin 实验性的 还有
kotlin {
experimental {
coroutines "enable"
}
}
这个答案可能比 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
}
Activity
/Fragment
中有几个有用的工具可用于长时间 运行ning API-calls。所以基本上如果你想 运行 两个长 运行ning 并行任务并在完成后更新 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
块 运行ning。
我刚开始学习 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
方法。
不幸的是,我无法在 the official tutorial for coroutines.
上找到有关交付某些特定线程结果的任何信息编辑:
另见 an official example in Kotlin repo
你需要实施Continuation interface which makes a callback onto Android UI thread and Coroutine context
例如(来自 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)
}
然后尝试:
launch(Android) {
val aVal = a.await()
val bVal = b.await()
resultTV.setText((aVal * bVal).toString())
}
更多信息:
您应将代码中的 < NEED UI thread here >
替换为 kotlinx.coroutines project. Its usage is explained in the Guide to UI programming with coroutines 的 kotlinx-coroutines-android
模块中的 UI
上下文,并提供大量示例。
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;
}
首先包含为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)
}
}
对于那些在 gradle
中使用 kotlin experimental 公开项目的人 .aar 或 .apk 到其他项目模块 - 请记住,当您使用 kotlin experimental parent modules/project 必须接受 kotlin 实验性的 还有
kotlin {
experimental {
coroutines "enable"
}
}
这个答案可能比 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
}
Activity
/Fragment
中有几个有用的工具可用于长时间 运行ning API-calls。所以基本上如果你想 运行 两个长 运行ning 并行任务并在完成后更新 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
块 运行ning。