在 Android 中将全局变量与协程一起使用
Using Global Variables with Coroutines in Android
我有以下代码,我在其中启动协程来处理数据检索并将它们存储到本地数据库中:
private var lastRequestedPage = 1
private var isRequestInProgress = false
private val viewModelScope = CoroutineScope(viewModelJob + Dispatchers.Main)
// function that is called from several places
private fun requestAndSaveData(){
if(isRequestInProgress) return
isRequestInProgress = true
viewModelScope.launch{
withContext(Dispatchers.IO){
// 2 heavyweight operations here:
// retrieve some data via Retrofit & place it into the local data via Room Persistence Library
}
}
lastRequestedPage++
isRequestInProgress = false
}
代码段说明
网络调用和数据库操作是基于一个名为isRequestInProgress
的布尔值完成的。当它为 false 时,它被设置为 true,网络和数据库操作可以由协程启动,之后 lastRequestedPage
递增,然后我们再次将 isRequestInProgress
设置为 false,这样整个过程就可以由程序从任何地方再次启动。
请注意,lastRequestedPage
作为参数传递给 Retrofit 网络调用函数,因为数据来自的 Web 服务使用分页(为了简洁起见,我将其省略)。
我的问题
我可以假设此逻辑适用于这样的协程吗?我可以用这样的解决方案遇到一些不好的线程问题吗?我问是因为我是协程概念的新手,我正在从我的另一个项目中改编这个逻辑,在这个项目中,我使用带有 Retrofit 的侦听器和回调来执行异步工作(每当调用 Retrofit#onResponse
方法时,我都会递增 lastRequestedPage
变量并将 isRequestInProgress
设置回 false)。
简短回答:不,这行不通。这是不正确的。
当您调用 viewModelScope.launch { }
或 GlobalScope.launch { }
时,launch
中的块将被挂起。然后程序流转到下一条语句。
在您的情况下,viewModelScope.launch { }
将暂停对 withContext(...)
的调用并继续执行 lastRequestedPage++
语句。
它会立即增加 lastRequestedPage
并切换 isRequestInProgress
标志,甚至在实际开始请求之前。
您要做的是将这些语句移到 launch { }
块中。
流程是这样的。
- 主线程
- 暂停块(启动调用)
- 继续前进 - 不关心挂起的块 - 进行 UI 更改等
要更好地了解其工作原理,请尝试此代码。
Log.d("cor", "Hello, world!") // Main thread
GlobalScope.launch {
// Suspend this block. Wait for Main thread to be available
Log.d("cor", "I am inside the launch block") // Main thread
withContext(Dispatchers.IO) {
delay(100L) // IO thread
Log.d("cor", "I am inside the io block") // IO thread
}
Log.d("cor", "IO work is done") // Back to main thread
}
Log.d("cor", "I am outside the launch block") // Main thread
输出为
D/cor: Hello, world!
D/cor: I am outside the launch block
D/cor: I am inside the launch block
D/cor: I am inside the io block
D/cor: IO work is done
我有以下代码,我在其中启动协程来处理数据检索并将它们存储到本地数据库中:
private var lastRequestedPage = 1
private var isRequestInProgress = false
private val viewModelScope = CoroutineScope(viewModelJob + Dispatchers.Main)
// function that is called from several places
private fun requestAndSaveData(){
if(isRequestInProgress) return
isRequestInProgress = true
viewModelScope.launch{
withContext(Dispatchers.IO){
// 2 heavyweight operations here:
// retrieve some data via Retrofit & place it into the local data via Room Persistence Library
}
}
lastRequestedPage++
isRequestInProgress = false
}
代码段说明
网络调用和数据库操作是基于一个名为isRequestInProgress
的布尔值完成的。当它为 false 时,它被设置为 true,网络和数据库操作可以由协程启动,之后 lastRequestedPage
递增,然后我们再次将 isRequestInProgress
设置为 false,这样整个过程就可以由程序从任何地方再次启动。
请注意,lastRequestedPage
作为参数传递给 Retrofit 网络调用函数,因为数据来自的 Web 服务使用分页(为了简洁起见,我将其省略)。
我的问题
我可以假设此逻辑适用于这样的协程吗?我可以用这样的解决方案遇到一些不好的线程问题吗?我问是因为我是协程概念的新手,我正在从我的另一个项目中改编这个逻辑,在这个项目中,我使用带有 Retrofit 的侦听器和回调来执行异步工作(每当调用 Retrofit#onResponse
方法时,我都会递增 lastRequestedPage
变量并将 isRequestInProgress
设置回 false)。
简短回答:不,这行不通。这是不正确的。
当您调用 viewModelScope.launch { }
或 GlobalScope.launch { }
时,launch
中的块将被挂起。然后程序流转到下一条语句。
在您的情况下,viewModelScope.launch { }
将暂停对 withContext(...)
的调用并继续执行 lastRequestedPage++
语句。
它会立即增加 lastRequestedPage
并切换 isRequestInProgress
标志,甚至在实际开始请求之前。
您要做的是将这些语句移到 launch { }
块中。
流程是这样的。
- 主线程
- 暂停块(启动调用)
- 继续前进 - 不关心挂起的块 - 进行 UI 更改等
要更好地了解其工作原理,请尝试此代码。
Log.d("cor", "Hello, world!") // Main thread
GlobalScope.launch {
// Suspend this block. Wait for Main thread to be available
Log.d("cor", "I am inside the launch block") // Main thread
withContext(Dispatchers.IO) {
delay(100L) // IO thread
Log.d("cor", "I am inside the io block") // IO thread
}
Log.d("cor", "IO work is done") // Back to main thread
}
Log.d("cor", "I am outside the launch block") // Main thread
输出为
D/cor: Hello, world!
D/cor: I am outside the launch block
D/cor: I am inside the launch block
D/cor: I am inside the io block
D/cor: IO work is done