Coroutine launch() 而在另一个协程中
Coroutine launch() while inside another coroutine
我有以下代码(伪代码)
fun onMapReady()
{
//do some stuff on current thread (main thread)
//get data from server
GlobalScope.launch(Dispatchers.IO){
getDataFromServer { result->
//update UI on main thread
launch(Dispatchers.Main){
updateUI(result) //BREAKPOINT HERE NEVER CALLED
}
}
}
}
如评论所述,代码从未进入协程调度到主队列。但是,如果我明确使用 GlobalScope.launch(Dispatchers.Main)
而不仅仅是 launch(Dispatchers.Main)
,则下面的内容有效
fun onMapReady()
{
//do some stuff on current thread (main thread)
//get data from server
GlobalScope.launch(Dispatchers.IO){
getDataFromServer { result->
//update UI on main thread
GlobalScope.launch(Dispatchers.Main){
updateUI(result) //BREAKPOINT HERE IS CALLED
}
}
}
}
为什么第一种方法不起作用?
我认为这里的问题是 getDataFromServer()
是异步的,它立即 returns 因此您在退出 GlobalScope.launch(Dispatchers.IO) { ... }
块后调用 launch(Dispatchers.Main)
。换句话说:您尝试使用已经完成的协程范围启动协程。
我的建议是不要像这样将异步的、基于回调的 API 与协同程序混合使用。协程最适合与同步的挂起函数一起使用。此外,如果您更喜欢异步执行所有内容并独立于其他任务(您的 onMapReady()
启动了 3 个独立的异步操作),那么我认为协程根本不是一个好的选择。
关于您的示例:您确定不能直接从主线程执行 getDataFromServer()
吗?它不应该阻塞主线程,因为它是异步的。同样,在某些库中,回调会在主线程中自动执行,在这种情况下,您的示例可以替换为:
fun onMapReady() {
getDataFromServer { result->
updateUI(result)
}
}
如果结果在后台线程中执行,那么您可以像以前一样使用 GlobalScope.launch(Dispatchers.Main)
,但这并不是我们使用协程的通常方式。或者您可以使用实用程序,例如runOnUiThread() 在 Android 上,这可能更有意义。
@broot 已经解释了问题的要点。您正在尝试 launch
外部 GlobalScope.launch
的子作用域中的协程,但是当调用 getDataFromServer
的回调时该作用域已经完成。
所以简而言之,不要在将在您无法控制的 place/time 中调用的回调中捕获外部范围。
处理您的问题的更好方法是使 getDataFromServer
挂起而不是基于回调。如果它是您无法控制的 API,您可以通过这种方式创建一个暂停包装器:
suspend fun getDataFromServerSuspend(): ResultType = suspendCoroutine { cont ->
getDataFromServer { result ->
cont.resume(result)
}
}
然后您可以简化调用代码:
fun onMapReady() {
// instead of GlobalScope, please use viewModelScope or lifecycleScope,
// or something more relevant (see explanation below)
GlobalScope.launch(Dispatchers.IO) {
val result = getDataFromServer()
// you don't need a separate coroutine, just a context switch
withContext(Dispatchers.Main) {
updateUI(result)
}
}
}
附带说明一下,GlobalScope
可能不是您想要的。您应该改为使用映射到视图或视图模型生命周期的范围(viewModelScope
或 lifecycleScope
),因为如果视图被销毁,您对该协程的结果不感兴趣(因此它应该取消)。如果由于某种原因某些东西在协程内挂起或循环,这将避免协程泄漏。
我有以下代码(伪代码)
fun onMapReady()
{
//do some stuff on current thread (main thread)
//get data from server
GlobalScope.launch(Dispatchers.IO){
getDataFromServer { result->
//update UI on main thread
launch(Dispatchers.Main){
updateUI(result) //BREAKPOINT HERE NEVER CALLED
}
}
}
}
如评论所述,代码从未进入协程调度到主队列。但是,如果我明确使用 GlobalScope.launch(Dispatchers.Main)
而不仅仅是 launch(Dispatchers.Main)
fun onMapReady()
{
//do some stuff on current thread (main thread)
//get data from server
GlobalScope.launch(Dispatchers.IO){
getDataFromServer { result->
//update UI on main thread
GlobalScope.launch(Dispatchers.Main){
updateUI(result) //BREAKPOINT HERE IS CALLED
}
}
}
}
为什么第一种方法不起作用?
我认为这里的问题是 getDataFromServer()
是异步的,它立即 returns 因此您在退出 GlobalScope.launch(Dispatchers.IO) { ... }
块后调用 launch(Dispatchers.Main)
。换句话说:您尝试使用已经完成的协程范围启动协程。
我的建议是不要像这样将异步的、基于回调的 API 与协同程序混合使用。协程最适合与同步的挂起函数一起使用。此外,如果您更喜欢异步执行所有内容并独立于其他任务(您的 onMapReady()
启动了 3 个独立的异步操作),那么我认为协程根本不是一个好的选择。
关于您的示例:您确定不能直接从主线程执行 getDataFromServer()
吗?它不应该阻塞主线程,因为它是异步的。同样,在某些库中,回调会在主线程中自动执行,在这种情况下,您的示例可以替换为:
fun onMapReady() {
getDataFromServer { result->
updateUI(result)
}
}
如果结果在后台线程中执行,那么您可以像以前一样使用 GlobalScope.launch(Dispatchers.Main)
,但这并不是我们使用协程的通常方式。或者您可以使用实用程序,例如runOnUiThread() 在 Android 上,这可能更有意义。
@broot 已经解释了问题的要点。您正在尝试 launch
外部 GlobalScope.launch
的子作用域中的协程,但是当调用 getDataFromServer
的回调时该作用域已经完成。
所以简而言之,不要在将在您无法控制的 place/time 中调用的回调中捕获外部范围。
处理您的问题的更好方法是使 getDataFromServer
挂起而不是基于回调。如果它是您无法控制的 API,您可以通过这种方式创建一个暂停包装器:
suspend fun getDataFromServerSuspend(): ResultType = suspendCoroutine { cont ->
getDataFromServer { result ->
cont.resume(result)
}
}
然后您可以简化调用代码:
fun onMapReady() {
// instead of GlobalScope, please use viewModelScope or lifecycleScope,
// or something more relevant (see explanation below)
GlobalScope.launch(Dispatchers.IO) {
val result = getDataFromServer()
// you don't need a separate coroutine, just a context switch
withContext(Dispatchers.Main) {
updateUI(result)
}
}
}
附带说明一下,GlobalScope
可能不是您想要的。您应该改为使用映射到视图或视图模型生命周期的范围(viewModelScope
或 lifecycleScope
),因为如果视图被销毁,您对该协程的结果不感兴趣(因此它应该取消)。如果由于某种原因某些东西在协程内挂起或循环,这将避免协程泄漏。