Идея сопрограммы в котлине заключалась в том, чтобы отвлечь понятие подвески и обратных вызовов и написать простой последовательный код. Вам не нужно беспокоиться о том, приостановлена ли сопрограмма или нет, подобно потокам.
Какова цель suspendCoroutineOrReturn
и COROUTINE_SUSPENDED
и в каком случае вы могли бы использовать их?
suspendCoroutineOrReturn
и COROUTINE_SUSPENDED
были введены совсем недавно в 1.1 для решения конкретной проблемы переполнения стека. Вот пример:
fun problem() = async { repeat(10_000) { await(work()) } }
Где await
просто ждет завершения:
suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Unit { f.whenComplete { value, exception -> // <- await$lambda if (exception != null) c.resumeWithException(exception) else c.resume(value) } }
Давайте посмотрим на случай, когда work
не приостановлена, но сразу же возвращает результат (например, кэшируется). problem$stateMachine
автомат, на котором компилируются сопрограммы в Котлин, собирается сделать следующие вызовы: problem$stateMachine
, problem$stateMachine
, CompletableFuture.whenComplete
, await$lambda
, ContinuationImpl.resume
, problem$stateMachine
, await
, …
По сути, ничто никогда не приостанавливается, и государственный автомат снова и снова запускает себя в том же потоке выполнения, который заканчивается StackOverflowError
.
Предлагаемое решение состоит в том, чтобы разрешить await
возврата специального токена ( COROUTINE_SUSPENDED
), чтобы отличить, действительно ли приостановлена или нет COROUTINE_SUSPENDED
, так что COROUTINE_SUSPENDED
автомат мог избежать переполнения стека. Затем, suspendCoroutineOrReturn
для контроля выполнения coroutine. Вот его заявление:
public inline suspend fun <T> suspendCoroutineOrReturn(crossinline block: (Continuation<T>) -> Any?): T
Обратите внимание, что он получает блок, который снабжен продолжением. В принципе, это способ доступа к экземпляру Continuation
, который обычно скрывается и появляется только во время компиляции. COROUTINE_SUSPENDED
также разрешено возвращать любое значение или COROUTINE_SUSPENDED
.
Поскольку это все выглядит довольно сложно, Котлин пытается скрыть его и рекомендует использовать только функцию suspendCoroutine
, которая внутренне делает все, что упомянуто выше для вас. Вот правильная реализация await
которая позволяет избежать StackOverflowError
(сторона примечание: await
отправлено в Kotlin lib, и это фактически функция расширения, но это не так важно для этого обсуждения)
suspend fun <T> await(f: CompletableFuture<T>): T = suspendCoroutine { c -> f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } }
Но если вы когда-либо захотите взять на себя мелкий контроль над продолжением сопрограммы, вы должны вызвать suspendCoroutineOrReturn
и вернуть COROUTINE_SUSPENDED
всякий раз, когда выполняется внешний вызов.