Kotlin并行协程。

4

在不同的协程中保存多个作业实例是否可行?假设我想同时运行几个不相关且不能在一个协程中发生的协程,但我希望它们并行运行。在Android中,我应该保存作业实例,以便在onDestroy方法中取消作业。将每个作业单独保存在列表中是否可接受,还是违反了某些规则。我知道在RX中有订阅,为什么Kotlin Coroutines中没有相应的功能?

val jobList = arrayListOf<Job>()

fun startJob1() {
    jobList.add(launch {
        //do some work
    })

fun startJob1() {
    jobList.add(launch {
        //do some other unrelated work
    })

override fun onDestroy() {
    super.onDestroy()
    cancelAllActiveJobs(jobList)
}

这种架构对于协程有意义吗?

2个回答

6
您可以手动维护一个启动的Job对象列表,但您也可以使用现成的父-子Job层次结构来更轻松地管理和维护已启动的作业列表。

因此,首先,您需要定义一个指向父作业的引用,而不是一份工作清单:

val job = Job()

然后,每次启动一个新的协程时,您都将其作为此作业的子项

fun startJob1() {
    launch(job) { // make it a child
        //do some work
    }
}

fun startJob1() {
    launch(job) { // make it a child
        //do some other unrelated work
    }
}

最后,当您需要销毁对象并取消所有作业时,只需取消父作业即可。
override fun onDestroy() {
    super.onDestroy()
    job.cancel()
}

这种方法的优点是工作列表会自动管理。新的协程可以启动并添加到父级作业中,当它们完成时,它们会自动从父级作业中删除。
您可以在指南的相应部分阅读更多内容:https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md#cancellation-via-explicit-job

这样,您仍然每次只有一个父作业。我的问题是关于拥有父作业列表的问题。我甚至给了一个我正在质疑的例子。 - BigApeWhat
1
我在答案中提供的代码与您原来的代码完全相同。它仍然运行多个作业,它们仍然被保存在列表中。试试看吧。不同之处在于您无需显式地管理此列表,因为所有作业都是子进程,父进程会自动保持其列表。 - Roman Elizarov
有道理,是否可以在UI线程上使用Job()上下文呢? - BigApeWhat
1
你可以使用 launch(UI + job) - Roman Elizarov
如何使用Anko Coroutine扩展来实现这一点?我们只需要使用asReference()来避免数据/上下文泄漏。 - Nicolas Jafelle
1
在这种方法中,有一种取消特定协程的方式吗?比如给一个ID然后取消它? - Lucas Sousa

3
这是完全可行的,也没有什么特别之处。看看这个简单的例子,一次性创建了100k个工作岗位:
val jobs = List(100_000) { // launch a lot of coroutines and list their jobs
        launch {
            delay(1000L)
            print(".")
        }
    }
 jobs.forEach { it.join() } 

为使工作可以取消,它本身必须检查是否已经被外部取消,您可以通过循环活动状态来实现此操作:while (isActive)

这里有一个例子,其中包含两个在之后被取消的工作:

fun main(args: Array<String>) = runBlocking {
    val startTime = System.currentTimeMillis()
    val jobs = arrayListOf<Job>()
    jobs += launch {
        var nextPrintTime = startTime
        var i = 0
        while (isActive) { // check if still active
            if (System.currentTimeMillis() >= nextPrintTime) {
                println("Job1: Sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }

    //another job
    jobs += launch {
        while (isActive) { // check if still active
            if (System.currentTimeMillis() >= 42) {
                println("Job2: Sleeping 42 ...")
                delay(500L)
            }
        }
    }
    delay(1300L) // delay a bit
    println("main: Cancelling the sleeping job!")
    jobs.forEach { it.cancelAndJoin() } // cancels the job and waits for its completion
}

我知道它是可行的,但是否正确?协程应该是这样使用的吗?在 Android 中,我将不得不处理每个作业的状态,并处理重复作业是否为我想要忽略的错误,还是应该再次运行它。 - BigApeWhat
是的,绝对没问题。为了使任务可取消,它必须自行检查是否已被外部取消,您可以使用活动状态的循环来实现:while (isActive) - s1m0nw1
你正在从主协程中保存所有子作业,并在函数结束时取消它们。我更担心的是保存多个父作业,知道何时取消它们并确保不存在重复项。这些父作业将在 onDestroy 时被取消。这是两件非常不同的事情。 - BigApeWhat
抱歉,我不明白你的问题。 - s1m0nw1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接