Почему мне все еще нужно добавить job.join() после того, как я использовал runBlocking в Kotlin?

Я изучаю сопрограммы котлина.

Изображение А может получить правильный результат.

Я думаю, что я использовал код runBlocking, и основная функция будет продолжать работать, пока не получит окончательный результат, но изображение B не удалось, почему?

Изображение А

введите здесь описание изображения

Изображение B введите здесь описание изображения


person HelloCW    schedule 18.03.2020    source источник


Ответы (4)


Из документации kotlin об основах сопрограмм:

Структурированный параллелизм

Есть еще кое-что, что можно пожелать для практического использования сопрограмм. Когда мы используем GlobalScope.launch, мы создаем сопрограмму верхнего уровня. Несмотря на то, что он легкий, он все же потребляет некоторые ресурсы памяти во время работы. Если мы забудем сохранить ссылку на только что запущенную сопрограмму, она все равно будет работать. Что делать, если код в сопрограмме зависает (например, мы ошибочно слишком долго задерживаемся), что, если мы запустили слишком много сопрограмм и не хватило памяти? Необходимость вручную сохранять ссылки на все запущенные сопрограммы и присоединяться к ним чревата ошибками.

Есть лучшее решение. Мы можем использовать структурированный параллелизм в нашем коде. Вместо запуска сопрограмм в GlobalScope, как мы обычно делаем с потоками (потоки всегда глобальны), мы можем запускать сопрограммы в конкретной области выполняемой операции.

В нашем примере у нас есть основная функция, которая превращается в сопрограмму с помощью построителя сопрограмм runBlocking. Каждый построитель сопрограмм, включая runBlocking, добавляет экземпляр CoroutineScope в область действия своего блока кода. Мы можем запускать сопрограммы в этой области без необходимости явного присоединения к ним, потому что внешняя сопрограмма (в нашем примере runBlocking) не завершится, пока не завершатся все сопрограммы, запущенные в ее области. Таким образом, мы можем упростить наш пример:

import kotlinx.coroutines.*

fun main() = runBlocking { // this: CoroutineScope
    launch { // launch a new coroutine in the scope of runBlocking
        delay(1000L)
        println("World!")
    }
    println("Hello,")
}
person Diego Marin Santos    schedule 18.03.2020

Когда вы используете runBlocking, ваш код между { } будет выполняться внутри CoroutineScope. Если вы запустите дочернюю сопрограмму внутри с launch, она будет вести себя так, как вы ожидали, потому что родительская сопрограмма должна дождаться всех своих дочерних элементов перед завершением:

import kotlinx.coroutines.*

fun main() = runBlocking {
    launch {
        delay(1000L)
        println("World!")
    }
    println("Hello, ")
}

Однако, когда вы используете GlobalScope.launch для запуска новой сопрограммы, она не будет привязана ни к какому родителю, и поэтому не будет ожидаться, пока вы не добавите job.join().

См. это. :

Дети сопрограммы

Когда сопрограмма запускается в CoroutineScope другой сопрограммы, она наследует свой контекст через CoroutineScope.coroutineContext, и задание новой сопрограммы становится дочерним по отношению к заданию родительской сопрограммы. Когда родительская сопрограмма отменяется, все ее потомки также рекурсивно отменяются.

Однако, когда GlobalScope используется для запуска сопрограммы, для задания новой сопрограммы не существует родителя. Поэтому он не привязан к области, из которой был запущен, и работает независимо.

person huytc    schedule 18.03.2020

runBlocking не вернется, пока все сопрограммы в своей области не завершатся. Поскольку ваша работа выполняется на GlobalScope, она не ждет завершения. job.join() заставляет его ждать, пока задание (даже из другой области) не будет выполнено.

Если вы удалите GlobalScope., он запустится в своей внутренней области и будет работать так, как вы ожидаете.

person Kiskae    schedule 18.03.2020
comment
Спасибо! Могу ли я всегда добавлять job.join(), даже если он находится в своей области? - person HelloCW; 18.03.2020
comment
Да, хотя GlobalScope рекомендуется использовать только для действий типа «запустил и забыл», когда вы не взаимодействуете с процессом после его запуска, если вы зависите от происходящего действия, это не очень подходит. - person Kiskae; 18.03.2020

Добавление к точке, сделанной @huytc

Чтобы лучше понять иерархию родитель-потомок, вы можете использовать job.children.contains, как показано ниже.

val customScope = CoroutineScope(Dispatchers.Default)

fun main() = runBlocking {

val job = customScope.launch {
    println("processing")
    delay(1000)
    println("done processing")
}

println("Is custom scope child of run-blocking: ${this.coroutineContext.job.children.contains(job)}")



Output:
processing
Is custom scope child of run-blocking: false
person Veeresh Charantimath    schedule 13.06.2021