我觉得我缺少一些关键部分来理解下面这段代码的工作原理:
private fun retrieveAndStore() {
launch(UI) {
val service = retrofit.create(AWSService::class.java)
val response = service.retrieveData().await()
store(data = response)
}
}
private suspend fun store(data: JsonData) {
val db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "app-db").build()
db.appDao().insert(storyData)
}
这是我在运行时得到的错误:
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
我不明白为什么网络代码通过改造工作但存储功能失败。我希望有人能告诉我发生了什么事?
有趣的是,如果我使用async {}包装db调用。它可以工作,这是否意味着协同程序只能调用其他协同程序?
答案 0 :(得分:5)
协同程序不是在前台或后台运行。它们是关于被挂起的能力,就像本机线程被操作系统暂停一样,但是在你控制这种行为的层面上。
当你说launch(UI) { some code }
时,你告诉Kotlin将“一些代码”作为任务提交给GUI事件循环。它将在GUI线程上运行,直到明确暂停;唯一的区别是它不会立即运行,因此launch(UI)
块下面的下一行代码将在它之前运行。
当你的“某些代码”遇到suspendCoroutine
调用时,神奇的部分会出现:这是执行停止的地方,你在传递给suspendCoroutine
的块中得到一个延续对象。你可以随心所欲地使用该对象,通常将其存储在某个地方然后再继续使用。
通常您看不到suspendCoroutine
来电,因为它在您正在调用的某些suspend fun
的实现中,但您可以自由地实现自己的。{/ p>
一个这样的库函数是withContext
,它是解决问题所需的函数。它使用您传递的块创建另一个协同程序,将该协程提交给您指定的其他上下文(一个有用的示例是CommonPool
),然后挂起当前的协同程序,直到另一个完成。这正是您将阻塞调用转换为可挂起函数所需的内容。
在您的情况下,它看起来像这样:
private suspend fun store(data: JsonData) = withContext(CommonPool) {
val db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "app-db").build()
db.appDao().insert(storyData)
}
我还要补充一点,您最好创建自己的线程池而不是依赖于系统范围的CommonPool
。有关详细信息,请参阅this thread。