Прежде всего, вы неправильно понимаете концепцию suspend
функций. Вызов функции showLoginForm()
не запускает новую сопрограмму. Код в одной сопрограмме всегда выполняется последовательно - сначала вы вызываете showLoginForm()
, он задерживается, он не возобновляет продолжения, потому что loginContinuation
равен null
, а затем suspendCancellableCoroutine
навсегда приостанавливает вашу сопрограмму и вызывает тупик.
Запуск новой сопрограммы, которая выполняет showLoginForm()
, может заставить ваш код работать:
suspend fun CoroutineScope.postComment() {
if (!isLoggedIn) {
launch {
showLoginForm()
}
suspendCancellableCoroutine<Unit> {
loginContinuation = it
}
}
// call the api or whatever
delay(1000)
println("comment posted!")
}
Этот код все еще может завершиться ошибкой (*), но в данном конкретном случае это не так. Рабочая версия этого кода может выглядеть так:
import kotlin.coroutines.*
import kotlinx.coroutines.*
fun main(args: Array<String>) {
println("Hello, world!")
runBlocking {
postComment()
}
}
var isLoggedIn = false
suspend fun CoroutineScope.postComment() {
if (!isLoggedIn) {
suspendCancellableCoroutine<Unit> { continuation ->
launch {
showLoginForm(continuation)
}
}
}
delay(1000)
println("comment posted!")
}
suspend fun showLoginForm(continuation: CancellableContinuation<Unit>) {
println("show login form")
delay(1000)
println("delay over")
isLoggedIn = true
continuation.resume(Unit) { println("login cancelled") }
}
Кроме того, в вашем примере приостановка сопрограмм не требуется. Зачем нам нужна другая сопрограмма, если мы можем просто выполнить ее код в той же сопрограмме? Нам нужно подождать, пока все не закончится. Поскольку сопрограммы выполняют код последовательно, мы будем go кодировать после if
ветви только после завершения showLoginForm()
:
var isLoggedIn = false
suspend fun postComment() {
if (!isLoggedIn) {
showLoginForm()
}
delay(1000)
println("comment posted!")
}
suspend fun showLoginForm() {
println("show login form")
delay(1000)
println("delay over")
isLoggedIn = true
}
Этот подход является лучшим для вашего примера, где весь код является последовательным.
(*) - этот код все еще может вызвать взаимоблокировку, если suspendCancellableCoroutine
вызывается после завершения showLoginForm
- например, если вы удалите вызов delay
в showLoginForm
или если вы используете многопоточный диспетчер - в JVM нет гарантии, что suspendCancellableCoroutine
будет вызван раньше, чем showLoginForm
. Более того, loginContinuation
не является @Volatile
, поэтому в многопоточном диспетчере код может не работать также из-за проблем видимости - поток, выполняющий showLoginForm
, может заметить, что loginContinuation
равен null
.