【问题标题】:What happen with coroutines when main thread exits?当主线程退出时协程会发生什么?
【发布时间】:2019-02-11 06:02:42
【问题描述】:

在 Java 中,当主线程退出时,所有用户线程(非守护线程)将继续运行,直到它们完成工作。

我有一个简单的程序,可以将 1 到 5 的计数器打印到控制台。

Java 版本:

fun main(args: Array<String>) {
    println("Main start")

    countWithThread()

    println("Main end")
}

fun countWithThread() {
    Thread(Runnable {
        for (i in 1..5) {
            println("${Thread.currentThread().name} count $i")
            Thread.sleep(10)
        }
    }).start()
}

输出:

Main start
Main end
Thread-0 count 1
Thread-0 count 2
Thread-0 count 3
Thread-0 count 4
Thread-0 count 5

Process finished with exit code 0

Kotlin 版本:

fun main(args: Array<String>) {
    println("Main start")

    countWithCoroutine()

    println("Main end")
}

fun countWithCoroutine() {
    launch(CommonPool) {
        for (i in 1..5) {
            println("${Thread.currentThread().name} count $i")
            delay(10)
        }
    }
}

输出:

Main start
Main end

Process finished with exit code 0

如您所见,当主线程退出时,协程中的代码不再运行。似乎 Kotlin 在后台终止了所有协程。

谁能告诉我当主线程退出时协程到底发生了什么?

【问题讨论】:

    标签: kotlin kotlin-coroutines


    【解决方案1】:

    协程本身并没有以 JVM 知道的方式“运行”。它们只是堆上的对象。

    但是,协程上下文 确实对何时允许 JVM 终止有发言权。创建你自己的:

    val threadPool = Executors.newFixedThreadPool(4)
    val dispatcher = threadPool.asCoroutineDispatcher()
    

    现在如果你用它代替CommonPool:

    launch(dispatcher) { ... }
    

    您会发现 JVM 根本没有死机,即使所有任务都已完成。只有当你明确说它才会退出

    threadPool.shutdown()
    

    但是请注意,executor.shutdown() 对协程的行为与对您提交给它的“经典”任务的行为不同。 executor 会确保所有提交的任务在关闭前都完成,但它不考虑挂起的协程。

    【讨论】:

    • 所以协程将被杀死或继续运行基于coroutine context 实现。如果协程上下文中的所有线程都是守护线程(例如,CommonPool),那么它将被杀死,如果协程上下文中的所有线程都是用户线程,那么它将继续运行直到完成它们的工作。每次我尝试使用协程上下文时,我都必须看到它的底层实现。我的理解正确吗?
    • 只要有单个非守护线程,JVM就会一直运行。协程并不总是绑定到单个上下文,使用 withContext 您可以临时将其发送到另一个上下文。此外,与您提交到池的 Java 任务不同,发出 pool.shutdown() 并不能确保所有协程在关闭之前都已完成。这是因为挂起的协程在线程池中的任何地方都没有考虑,它在 Kotlin 的结构中。
    • "pool.shutdown() won't ensure all the coroutines are done before shutting down"。这是否意味着协程将被杀死但它可能在inconsistent state
    • 尽量避免考虑“杀死”协程。一个挂起的协程只不过是一个带有resume() 方法的对象,你可以调用也可以不调用。您不必采取任何有力的行动来“杀死”它;你必须做出一些明确的努力才能让它再次运行。在大多数情况下,它是一些框架代码,但基本原则是不变的。
    【解决方案2】:

    协程在主线程完成执行并且进程/JVM实例死亡时终止,它们就像守护线程一样。参考官方协程指南中的this section

    【讨论】:

    • 这是否意味着CommonPool中所有运行协程的线程默认都是deamon-threads。
    • 是的,当您在使用守护线程的 JVM 上运行时,CommonPoolForkJoinPool
    猜你喜欢
    • 2017-06-12
    • 2013-10-13
    • 1970-01-01
    • 2017-02-14
    • 2010-12-12
    • 1970-01-01
    相关资源
    最近更新 更多