Kotlin 如何正确加入在协同作用域中启动的所有作业

Kotlin 如何正确加入在协同作用域中启动的所有作业,kotlin,kotlin-coroutines,coroutinescope,Kotlin,Kotlin Coroutines,Coroutinescope,我正在将当前在GlobalScope上启动协同路由的一些Kotlin代码重构为基于结构化并发的方法。在JVM退出之前,我需要加入代码中启动的所有作业。我的类可以分解为以下接口: 接口异步任务器{ 有趣的工作(arg:Long) 暂停所有 } 用法: fun main(args:Array){ val asyncTasker=createAsyncTasker() asyncTasker.spawnJob(100) asyncTasker.spawnJob(200) asyncTasker.sp

我正在将当前在
GlobalScope
上启动协同路由的一些Kotlin代码重构为基于结构化并发的方法。在JVM退出之前,我需要加入代码中启动的所有作业。我的类可以分解为以下接口:

接口异步任务器{
有趣的工作(arg:Long)
暂停所有
}
用法:

fun main(args:Array){
val asyncTasker=createAsyncTasker()
asyncTasker.spawnJob(100)
asyncTasker.spawnJob(200)
asyncTasker.spawnJob(300)
asyncTasker.spawnJob(500)
//加入所有作业,因为它们将在JVM退出时被杀死
运行阻塞{
asyncTasker.joinAll()
}
}
基于我的
GlobalScope
的实现如下所示:

class GlobalScopeAsyncTasker:AsyncTasker{
private val pendingJobs=mutableSetOf()
覆盖作业(参数:长){
变量作业:作业?=null
job=GlobalScope.launch(Dispatchers.IO){
someSuspendFun(arg)
挂起作业。删除(作业)
}
挂起作业。添加(作业)
}
覆盖挂起所有(){
//迭代集合的副本作为
//当我们加入作业时,作业将从集合中移除
pendingJobs.toSet().joinAll()
}
}
显然,这并不理想,因为跟踪每一个待处理的作业并不是很优雅,而且是旧的基于线程的编码范例的残余

作为一种更好的方法,我正在创建自己的
CoroutineScope
,用于启动所有子项,提供一个
SupervisorJob

类结构DConcurrencyAsyncTasker:AsyncTasker{
private val parentJob=SupervisorJob()
private val scope=CoroutineScope(Dispatchers.IO+parentJob)
覆盖作业(参数:长){
范围.发射{
someSuspendFun(arg)
}
}
覆盖挂起所有(){
parentJob.complete()//来自
Job#join()

当作业因任何原因完成时,[…]将继续此调用

由于我从未将父作业标记为
Completed
join
永远不会返回,即使作业的所有子作业都
已完成

考虑到作业无法将状态从
已完成
切换回
活动
,这是有意义的,因此,如果在所有子作业都已完成时,作业自动将状态切换到
已完成
,则不可能在以后添加更多子作业


感谢您为我指明了正确的方向。

我想知道这种行为将来是否会改变。目前,链接问题的答案仍然有效。目前,parentJob.join()
不加入其子项。对我来说,以下部分是深入挖掘的原因:

请注意,只有当作业的所有子项都完成时,作业才会完成

请注意,启动的协同程序作业可能处于“已完成”之外的另一种状态。您可能希望在执行
parentJob.children.forEach{println(it)}
(或您希望检查或调试它的任何信息;-)之前,通过类似于
parentJob.join()
-语句的方式验证这一点

有(至少?)两种方法可以确保所有已启动的子协同程序作业都已完成,这样它就不会挂起或过早完成:

  • 等待首先完成所有儿童工作(如评论中的链接答案所述),即:

    这不需要额外的
    parentJob.join()
    parentJob.complete()
    ,因此可能是首选的
    parentJob
    将在其所有子项完成时完成

  • 在调用
    加入之前调用
    完成
    ,即:

    parentJob.complete()
    parentJob.join()
    
    请注意,此处调用
    complete
    只是将状态转换为completing,正如中所述。在completing状态下,它也将等待其子项完成。如果您只调用
    complete()
    如果没有
    join
    程序可能会退出,甚至在运行您启动的协同程序作业之前。如果您仅使用
    join()
    它可能会无限期挂起,就像您已经体验到的那样


  • 这是否回答了您的问题?如果您没有明确说明父作业或其子作业已完成,它将永远运行…即,如果您只是在父作业上调用
    join
    ,它将等待所有子协同程序完成(文档中也有说明)。但是,启动的协同程序作业仍处于活动状态(或者至少没有完成)这就是为什么父作业挂在那里…但是我不知道为什么这样设计…@Roland来自
    作业
    文档:“协同程序作业是用启动协同程序生成器创建的。它运行指定的代码块,并在该块完成时完成。”由于当我加入时,
    someSuspendFun
    已经返回,子作业已经完成。事实上,它们甚至不再在
    parentJob
    children
    序列中。因此,如果我理解正确,父作业上的
    join
    只是挂起,因为它等待父作业本身完成,而我必须手动启动?奇怪的设计,但我想这是有道理的。我明白了为什么这种行为是有道理的。请查看我自己对这个问题的回答。感谢您的输入,@Roland!想发表评论……但时间比预期的长,所以我添加了一个答案……您可能需要在调用
    parentJob.joi之前验证作业的实际状态n()
    
    parentJob.complete()
    parentJob.join()