почему задержка делает совместную процедуру отменой - PullRequest
2 голосов
/ 02 мая 2020
fun main() = runBlocking {

    var i = 1

    var job = launch (Dispatchers.Default){

        println("Thread name  =  ${Thread.currentThread().name}")
        while (i < 10) { // replace i < 10 to isActive to make coroutine cancellable
            delay(500L)
//            runBlocking { delay(500L) }
            println("$isActive ${i++}")
        }
    }

    println("Thread name  =  ${Thread.currentThread().name}")
    delay(2000L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")

}

выход

Thread name  =  main
Thread name  =  DefaultDispatcher-worker-1
true 1
true 2
true 3
main: I'm tired of waiting!
main: Now I can quit.

если я использую runBlocking { delay(500L) }, то вышеуказанная совместная процедура не может быть отменена. Таким образом, он напечатает все значения до 9.

, но когда я использую delay(500L) автоматически, сопрограмма может быть отменена. Почему?

Ответы [ 3 ]

3 голосов
/ 02 мая 2020

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

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

Эта функция не должна использоваться из сопрограммы
runBlocking

Сопрограммы, как и потоки, не являются действительно прерываемыми; они должны полагаться на совместную отмену (см. также, почему остановка потоков - это плохо ). Отмена на самом деле тоже ничего не делает ; когда вы отменяете контекст, он просто уведомляет все свои дочерние контексты, которые будут продолжать отменять себя, и так далее.

Важно понимать, что runBlocking не является suspend функцией . Его нельзя приостановить, возобновить или отменить. По умолчанию родительский контекст не передается ему (EmptyCoroutineContext), поэтому сопрограмма, используемая для выполнения runBlocking, не будет реагировать на все, что происходит в восходящем направлении.

Когда вы пишете

while (i < 10) {
    runBlocking { delay(500L) }
    println("$isActive ${i++}")
}

здесь нет операций, которые можно отменить. Этот код никогда не проверяет, был ли отменен его контекст, и как таковой он будет продолжаться до завершения.

delay, однако, можно отменить ; как только родительский контекст отменяется, он немедленно возобновляется и выдает исключение.

Посмотрите на сгенерированный код:

@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
    switch (this.label) {
    case 0:
        while (i.element < 10) {
            BuildersKt.runBlocking$default( ... );
            ...
            System.out.println(var3);
        }

        return Unit.INSTANCE;
    default:
        throw new IllegalStateException( ... );
    }
}

Сравните это

while (i.element < 10) {
    BuildersKt.runBlocking$default( ... );
    ...
    System.out.println(var3);
}

с

do {
    ...
    System.out.println(var3);
    if (i.element >= 10) {
        return Unit.INSTANCE;
    }
    ...
} while (DelayKt.delay(500L, this) != var5);

Объявления переменных и аргументы для краткости опущены (...).


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

2 голосов
/ 02 мая 2020

Я немного изменил ваш код

try {
    while (i < 10) { // replace i < 10 to isActive to make coroutine cancellable
        delay(500L)
        println("$isActive ${i++}")
    }
} catch (e : Exception){
    println("Exception $e")
    if (e is CancellationException) throw e
}

вывод

Thread name  =  main
Thread name  =  DefaultDispatcher-worker-1
true 1
true 2
true 3
main: I'm tired of waiting!
Exception kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@40bcb892
main: Now I can quit.

вы можете увидеть исключение StandaloneCoroutine was cancelled его, потому что,

  • Если задание текущей сопрограммы отменено или завершено во время ожидания этой функции приостановки, то есть с задержкой (500 л), эта функция немедленно возобновляется с помощью CancellationException.

  • Итак, если Вы добавляете функцию приостановки внутри вашего launch, которую можно отменить.

Вы можете попробовать это с пользовательским приостановить веселье также

2 голосов
/ 02 мая 2020

Официальная документация гласит:

Все функции приостановки в kotlinx.coroutines можно отменить.

и delay - одна из них.

Вы можете проверить, что здесь .

Я думаю, что реальный вопрос должен быть: почему вложенный runBlocking не может быть отменен? по крайней мере попытка создать новую сопрограмму с runBlocking, когда isActive равен false, потерпит неудачу, хотя создание сопрограммы сопутствует вашей ответственности. Кроме того, runBlocking не следует использовать в первую очередь.

Получается, если вы передаете this.coroutineContext как CoroutineContext в runBlocking, оно отменяется:

fun main() = runBlocking {

    var i = 1

    var job = launch (Dispatchers.Default){

        println("Thread name  =  ${Thread.currentThread().name}")
        while (i < 10) { // replace i < 10 to isActive to make coroutine cancellable
            runBlocking(this.coroutineContext) { delay(500L) }
            println("$isActive ${i++}")
        }
    }

    println("Thread name  =  ${Thread.currentThread().name}")
    delay(2000L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")

}
...