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

Я изучаю сопрограммы kotlin.

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

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

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

enter image description here

Изображение B enter image description here

Ответы [ 3 ]

3 голосов
/ 18 марта 2020

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

import kotlinx.coroutines.*

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

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

См. this :

Потомки сопрограммы

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

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

2 голосов
/ 18 марта 2020

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

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

1 голос
/ 18 марта 2020

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

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

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

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

В нашем примере у нас есть основная функция, которая превращается в сопрограмму с помощью компоновщика 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,")
}
...