Что означает функция приостановки в Kotlin Coroutine

Я читаю Kotlin Coroutine и знаю, что он основан на функции suspend. Но что значит suspend?

Сопрограмма или функция приостанавливается?

С https://kotlinlang.org/docs/reference/coroutines.html

По сути, сопрограммы являются вычислениями, которые могут быть приостановлены без блокировки потока

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

🤔 Должны ли мы сказать, что сопрограмма приостановлена?

Какая сопрограмма будет приостановлена?

С https://kotlinlang.org/docs/reference/coroutines.html

Чтобы продолжить аналогию, await() может быть функцией приостановки (следовательно, также вызываемой из блока async {}), которая приостанавливает сопрограмму до тех пор, пока не выполнятся некоторые вычисления, и не вернет свой результат:

async { // Here I call it the outer async coroutine
    ...
    // Here I call computation the inner coroutine
    val result = computation.await()
    ...
}

🤔 В нем говорится "это приостанавливает сопрограмму до тех пор, пока не будут выполнены некоторые вычисления", но сопрограмма похожа на легкую нить. Так что, если сопрограмма приостановлена, как вычисления могут быть сделаны?

Мы видим, что await вызывается при computation, поэтому он может быть async который возвращает Deferred, что означает, что он может запустить другую сопрограмму

fun computation(): Deferred<Boolean> {
    return async {
        true
    }
}

🤔 Цитата говорит, что приостанавливает сопрограмму. Означает ли это suspend внешнюю async сопрограмму или suspend внутреннюю сопрограмму computation?

Означает ли suspend что в то время как внешняя async сопрограмма ожидает (await) завершения внутренней сопрограммы computation, она (внешняя async сопрограмма) бездействует (отсюда и имя приостановлено) и возвращает поток в пул потоков, а когда computation сопрограмма computation завершает работу он (внешняя async сопрограмма) просыпается, берет другой поток из пула и продолжает?

Причина, по которой я упоминаю эту тему, связана с https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html

Поток возвращается в пул во время ожидания сопрограммы, а когда ожидание завершено, сопрограмма возобновляется на свободном потоке в пуле

Ответ 1

Подвесные функции находятся в центре всего сопрограмм. Функция приостановки - это просто функция, которая может быть приостановлена и возобновлена позднее. Они могут выполнить длительную операцию и ожидать ее завершения без блокировки.

Синтаксис функции приостановки аналогичен синтаксису обычной функции, за исключением добавления ключевого слова suspend. Может принимать параметр и иметь тип возвращаемого значения. Однако функции приостановки могут быть вызваны только другой функцией приостановки или внутри сопрограммы.

suspend fun backgroundTask(param: Int): Int {
     // long running operation
}

Под капотом функции приостановки преобразуются компилятором в другую функцию без ключевого слова suspend, которое принимает дополнительный параметр типа Continuation. Например, приведенная выше функция будет преобразована компилятором в следующее:

fun backgroundTask(param: Int, callback: Continuation<Int>): Int {
   // long running operation
}

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

interface Continuation<in T> {
   val context: CoroutineContext
   fun resume(value: T)
   fun resumeWithException(exception: Throwable)
}

Ответ 2

Чтобы понять, что именно означает приостановить сопрограмму, я предлагаю вам пройти этот код:

import kotlinx.coroutines.Dispatchers.Unconfined
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

var continuation: Continuation<Int>? = null

fun main() = runBlocking {
    launch(Unconfined) {
        val a = a()
        println("Result is $a")
    }
    10.downTo(0).forEach {
        continuation!!.resume(it)
    }
}

suspend fun a(): Int {
    return b()
}

suspend fun b(): Int {
    while (true) {
        val i = suspendCoroutine<Int> { cont -> continuation = cont }
        if (i == 0) {
            return 0
        }
    }
}

Unconfined диспетчер сопрограмм устраняет магию диспетчеризации сопрограмм и позволяет нам сосредоточиться непосредственно на голых сопрограммах.

Код внутри блока launch начинает выполняться сразу в текущем потоке, как часть вызова launch. Что происходит следующим образом:

  1. Оценить val a = a()
  2. Это цепочки к b(), достигнув suspendCoroutine.
  3. Функция b() выполняет блок, переданный suspendCoroutine а затем возвращает специальное значение COROUTINE_SUSPENDED. Это значение не наблюдается через модель программирования Kotlin, но это то, что делает скомпилированный метод Java.
  4. Функция a(), видя это возвращаемое значение, сама также возвращает его.
  5. Блок launch делает то же самое, и теперь элемент управления возвращается к строке после вызова launch: 10.downTo(0)...

Обратите внимание, что в этот момент вы получаете такой же эффект, как если бы код внутри блока launch и ваш fun main код выполнялись одновременно. Просто так случается, что все это происходит в одном собственном потоке, поэтому блок launch "приостановлен".

Теперь внутри циклического кода forEach программа читает continuation, b() функцией b() и resumes его со значением 10. suspendCoroutine resume() реализована таким образом, что вызов вызова suspendCoroutine возвращен со значением, которое вы передали. Таким образом, вы неожиданно окажетесь в середине выполнения b(). Значение, которое вы передали resume() присваивается i и проверяется на 0. Если оно не равно нулю, цикл while (true) продолжается внутри b(), снова достигая suspendCoroutine, после чего ваш вызов resume() возвращается, и теперь вы проходите еще один шаг цикла в forEach(). Это продолжается до тех пор, пока, наконец, вы не продолжите с 0, затем выполняется оператор println и программа завершается.

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

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

Ответ 3

Сопрограмма или функция приостанавливается?

Вызов приостановить ИНГА функции приостановить ВЛЯЕТСЯ сопрограмму, что означает текущий поток может начать выполнение другой сопрограммы. Итак, говорят, что сопрограмма приостановлена, а не функция.

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

Какая сопрограмма приостанавливается?

Внешний async сопрограммы. Когда он вызывает computation(), внутренний async второй сопрограммы. Затем вызов await() приостанавливает выполнение внешней async сопрограммы, пока не завершится выполнение внутренней async сопрограммы.

Вы даже можете увидеть это с одним потоком: поток выполнит начало внешней async, затем вызовет computation() и достигнет внутренней async. В этот момент тело внутренней асинхронности пропускается, и поток продолжает выполнять внешнюю async пока не достигнет await(). await() - это "точка приостановки", потому что await - это функция приостановки. Это означает, что внешняя сопрограмма приостановлена, и, таким образом, поток начинает выполнять внутреннюю. Когда это сделано, он возвращается, чтобы выполнить конец внешнего async.

Означает ли приостановка, что в то время как внешняя асинхронная сопрограмма ожидает (ожидает) завершения внутренней сопрограммы вычислений, она (внешняя асинхронная сопрограмма) бездействует (отсюда и имя приостановлено) и возвращает поток в пул потоков, а когда дочерняя сопрограмма вычислений завершает работу, он (внешняя асинхронная сопрограмма) просыпается, берет другой поток из пула и продолжает?

Да, именно так

Ответ 4

Я обнаружил, что лучший способ понять suspend - провести аналогию между ключевым словом this и свойством coroutineContext.

Функции Kotlin могут быть объявлены как локальные или глобальные. Локальные функции магически имеют доступ к ключевому слову this, а глобальные - нет.

Функции Котлина могут быть объявлены как suspend или блокирующие. Функции suspend магическим образом имеют доступ к свойству coroutineContext, а блокирующие функции - нет.

Дело в том, что свойство coroutineContext объявляется как "обычное" свойство в Kotlin stdlib, но это объявление просто заглушка для документации/навигации. Фактически coroutineContext является встроенным внутренним свойством builtin intrinsic property, что означает, что магия компилятора под капотом знает об этом свойстве так же, как и о ключевых словах языка.

Ключевое слово this делает для локальных функций то, что свойство coroutineContext делает для функций suspend: оно дает доступ к текущему контексту выполнения - к контексту экземпляра класса в первом случае и к контексту экземпляра сопрограммы во втором случае.

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

Ответ 5

Я хотел бы дать вам простой пример концепции продолжения. Это то, что делает функция приостановки, она может заморозить/приостановить, а затем она продолжает/возобновляет. Перестаньте думать о сопрограмме с точки зрения потоков и семафоров. Думайте об этом с точки зрения продолжения и даже обратных вызовов.

Чтобы было ясно, судина может быть приостановлена с использованием подозрительной функции. давайте исследуем это:

в Android мы могли бы сделать это, например:

var TAG = "myTAG:"
        fun myMethod() {
            viewModelScope.launch(Dispatchers.Default) {
                for (i in 10..15) {
                    if (i == 10) { //on first iteration, we will completely FREEZE this coroutine (just for loop here gets 'suspended')
                        println("$TAG im a tired coroutine - let someone else print the numbers async. i'll suspend until your done")
                        freezePleaseIAmDoingHeavyWork()
                    } else
                        println("$TAG $i")
                }
            }

            //this area is not suspended, you can continue doing work
        }


        suspend fun freezePleaseIAmDoingHeavyWork() {
            withContext(Dispatchers.Default) {
                async {
                    //pretend this is a big network call
                    for (i in 1..10) {
                        println("$TAG $i")
                        delay(1_000)//delay pauses coroutine, NOT the thread. use  Thread.sleep if you want to pause a thread. 
                    }
                    println("$TAG phwww finished printing those numbers async now im tired, thank you for freezing, you may resume")
                }
            }
        }

который печатает следующее:

I: myTAG: my coroutine is frozen but i can carry on to do other things

I: myTAG: im a tired coroutine - let someone else print the numbers async. i'll suspend until your done

I: myTAG: 1
I: myTAG: 2
I: myTAG: 3
I: myTAG: 4
I: myTAG: 5
I: myTAG: 6
I: myTAG: 7
I: myTAG: 8
I: myTAG: 9
I: myTAG: 10

I: myTAG: phwww finished printing those numbers async now im tired, thank you for freezing, you may resume

I: myTAG: 11
I: myTAG: 12
I: myTAG: 13
I: myTAG: 14
I: myTAG: 15

представьте, что это работает так:

enter image description here

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

Я думаю, что этот сайт может помочь вам разобраться и является моей рекомендацией.

давайте сделаем что-нибудь классное и заморозим нашу функцию приостановки в середине итерации. мы возобновим это позже в onResume:

сохраните переменную продолжением и загрузите ее с объектом продолжения сопрограммы для нас:

var continuation: CancellableContinuation<String>? = null

suspend fun freezeHere() = suspendCancellableCoroutine<String> {
            continuation = it
        }

 fun unFreeze(){
            continuation?.resume("im resuming") {}
        }

Теперь давайте вернемся к нашей функции приостановки и остановим ее на середине итерации:

 suspend fun freezePleaseIAmDoingHeavyWork() {
        withContext(Dispatchers.Default) {
            async {
                //pretend this is a big network call
                for (i in 1..10) {
                    println("$TAG $i")
                    delay(1_000)
                    if(i == 3)
                        freezeHere() //dead pause, do not go any further
                }

            }
        }
    }

затем где-нибудь еще, как в onResume (например):

override fun onResume() {
        super.onResume()
        unFreeze()
    }

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