Firebase 在Kotlin中以同步方式运行异步任务
我正在尝试运行Firebase Firestore的“批处理”作业。由于批处理作业是异步的,每个批处理仅处理500个文档,因此我创建了一个批处理作业数组,希望以同步方式运行,以便准确地知道上一批何时完成,然后继续执行下一个操作 然而,为了在Kotlin中实现这一点,当我阅读时,我遇到了一些术语,如runBlocking、协同路由、Dispatcher、async、Wait、Context、Suspend、launch、join、Scope、Deferred、Continuation、CommonPool 此外,许多帖子说,在最新版本的Kotlin中,情况发生了变化。Kotlin谈到了runBlocking,但说runBlocking是件坏事 经过一些尝试和错误,我得到了这个得到编译Firebase 在Kotlin中以同步方式运行异步任务,firebase,kotlin,google-cloud-firestore,kotlin-coroutines,Firebase,Kotlin,Google Cloud Firestore,Kotlin Coroutines,我正在尝试运行Firebase Firestore的“批处理”作业。由于批处理作业是异步的,每个批处理仅处理500个文档,因此我创建了一个批处理作业数组,希望以同步方式运行,以便准确地知道上一批何时完成,然后继续执行下一个操作 然而,为了在Kotlin中实现这一点,当我阅读时,我遇到了一些术语,如runBlocking、协同路由、Dispatcher、async、Wait、Context、Suspend、launch、join、Scope、Deferred、Continuation、Common
suspend fun doTheThing() {
for ( b in batchArray) {
coroutineScope {
val job = async { b.commit() }
job.await()
}}
}
但是,现在我得到一个错误,说“Suspend function'doTheThing'应该只从一个协程或另一个Suspend函数调用”
我只是一时糊涂。我只想按顺序进行这些调用,或者等到所有调用都完成。不确定什么是正确的语法来完成这项工作,我错了什么概念
更新:以下代码段似乎正在运行:
for ( b in batchArray) {
runBlocking {b.commit()}
}
这样做是一种好的做法吗
这样做是一种好的做法吗
不,runBlocking
绝对是错误的做法。它将阻塞应用程序的主线程,并可能使用ANR使其崩溃。但是,您编写代码的特殊方式意味着您也可以删除运行阻塞
,并获得完全相同的行为b.commit()
是一个普通的异步调用,它会立即返回一个任务
对象,这意味着您尚未达到期望的目标,即在提交下一批之前等待批完成
现在,我们来看一个利用协同路由的正确解决方案
将org.jetbrains.kotlinx:kotlinx协同程序播放服务
依赖项放在类路径上。这将为您提供suspend fun Task.await()
扩展函数,并允许您构造一个挂起调用b.commit().await()
,该调用在提交批处理之前不会完成
有了它,您可以像这样编写函数:
fun CoroutineScope.doTheThing(batchArray: List<Batch>) {
launch {
for (b in batchArray) {
b.commit().await()
}
// add logic here for what to do when all batches are done
}
}
fun CoroutineScope.doTheThing(批处理数组:列表){
发射{
对于(batchArray中的b){
b、 commit().await()
}
//在此处添加逻辑,说明完成所有批处理后要执行的操作
}
}
为了调用它,您需要一个CoroutineScope
。如果您还不知道结构化并发以及如何使用它,请查看的文档以快速入门
请注意,
submitAll
的调用方在所有批处理完成之前不会阻塞,而是在后台启动一个协同程序并继续。然而,启动的协同程序将在批处理进行时挂起,完成后继续,开始下一个作业,挂起,等等,直到所有工作都完成。挂起时,它不会占用线程。协同路由通常由不同的构建器在某个协同路由范围的上下文中创建。与构建器一样,挂起函数在协同路由作用域中运行,因此应该在协同路由作用域中调用,协同路由作用域可以通过在协同路由、挂起函数中调用或从定义的作用域显式调用来提供
CoroutineScope是一个只包含一个属性的接口,该属性是。您可以通过实现CoroutineScope接口来创建自己的范围,并覆盖自己的coroutine上下文
val myCoroutineScope = object : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Job() + Dispatchers.Main
}
在您的范围内,您可以使用诸如launch、async、Product等构建器
您可以将函数重构为
suspend fun doTheThing() = coroutineScope{
for ( b in batchArray) {
b.commit()
}
}
fun main(args: Array<String>) {
myCoroutineScope.launch {
doTheThing()
println("Completed")
}
}
请参阅下面的解决方案,您需要在其中定义所有500个文档将在哪个线程上处理的批处理作业操作的主要功能。 因此,您可以使用IO dispatcher初始化一个协程作用域,并在其中调用主处理方法 科特林有三名调度员
- IO用于网络和磁盘IO相关工作
- 默认值用于复杂操作,如列表遍历或 数学运算
- Main用于将结果放置到UI或与UI相关的操作
fun initialFunction() {
//inside this function start the Coroutine using launch
//using Dispatcher.IO will perform execution of coroutine in background/IO
CoroutineScope(Dispatchers.IO).launch {
//call your method which will process batch job asynchronously
doTheThing()
}
}
suspend fun doTheThing() {
//now start your blocking call, this execute following block
//synchronously
runBlocking {
for ( b in batchArray) {
//commit will run synchronously and following nested coroutine
//will wait for job to get completed
launch {
val job = async { b.commit() }
job.await()
}
}
}
}
谢谢我用了你的第一个选择。它确实编译了,但是当我运行代码时,我得到了一个异常,说带有主调度器的
模块丢失了。添加提供主调度器的依赖项,例如“kotlinx coroutines android”
感谢您对范围的解释。另外,请注意解释什么是调度员以及这些不同的调度员是什么?调度员可以被认为是运行协同程序的东西。它们是为了协同路由,就像线程池是为了线程一样。协同程序中的不同调度程序是IO、默认和未限定。谢谢。不幸的是,我的代码遇到了一些其他问题,无法执行(在中提到)。我将在解决该问题后更新它的运行情况。谢谢。这与@dani chuks给出的协同程序示例相结合,似乎确实可以编译。由于中提到的一些问题,我无法执行。在我设法解决该问题后,将更新它的运行方式。请注意,@dani chucks向您展示了将CoroutineScope
引入应用程序的过时方式。我敦促您研究它的文档,它展示了一种更简单、更不容易出错的方法。谢谢。我现在正在阅读文档。虽然我必须说我在这件事上搞糊涂了
val myCoroutineScope = object : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = SupervisorJob() + Dispatchers.Default
}
fun initialFunction() {
//inside this function start the Coroutine using launch
//using Dispatcher.IO will perform execution of coroutine in background/IO
CoroutineScope(Dispatchers.IO).launch {
//call your method which will process batch job asynchronously
doTheThing()
}
}
suspend fun doTheThing() {
//now start your blocking call, this execute following block
//synchronously
runBlocking {
for ( b in batchArray) {
//commit will run synchronously and following nested coroutine
//will wait for job to get completed
launch {
val job = async { b.commit() }
job.await()
}
}
}
}