Ответ
Корутину можно возобновлять благодаря механизму continuation (продолжения). При приостановке (suspend) корутина сохраняет свое состояние (локальные переменные, точка остановки) в Continuation, а затем передает управление. Когда результат готов (например, после асинхронной операции), Continuation возобновляет выполнение с места остановки.
Пример:
suspend fun fetchData(): String {
return suspendCoroutine { continuation ->
someAsyncTask { result ->
continuation.resume(result)
}
}
}
Здесь suspendCoroutine создает Continuation, а resume() возобновляет выполнение.
Ответ 18+ 🔞
Давай разберём эту штуку, а то звучит как магия из сказки про ебучего Гарри Поттера. Короче, представь, что корутина — это не какой-нибудь тупой поток, который засыпает и просыпается, а хитрая жопа, которая умеет ставить себя на паузу и потом с того же места продолжить. Весь фокус в этой штуке под названием continuation (продолжение, ёпта).
Вот как это работает, без заумных слов: когда корутина доходит до suspend функции и ей надо подождать (скажем, пока с сервера ответ прилетит), она не тупо виснет, а делает умный трюк. Она берёт, выписывает на бумажку всё своё текущее состояние: какие у неё переменные, на какой строчке кода она остановилась, — и складывает эту бумажку в карман. Эта бумажка и есть Continuation. Потом она говорит: «Ладно, я пошла, разбудите меня, когда ответ будет». И управление возвращается туда, откуда её вызвали.
А когда тот самый долгожданный результат наконец-то готов (сервер ответил, файл прочитался, не важно), кто-то берёт эту самую бумажку-продолжение и тычет в неё пальцем: «Эй, просыпайся! Вот твой результат, продолжай работать». Этот пинок — вызов метода resume(). И корутина, как ни в чём не бывало, достаёт свою бумажку, смотрит, на какой строчке она остановилась, какие у неё были значения в переменных, и спокойно продолжает выполнение, будто и не прерывалась. Красота, да? Удивление пиздец просто.
Смотри на пример, тут всё наглядно:
suspend fun fetchData(): String {
return suspendCoroutine { continuation ->
someAsyncTask { result ->
continuation.resume(result)
}
}
}
Что тут происходит, по шагам:
- Ты вызываешь
fetchData(). Это suspend-функция, значит, её можно приостановить. - Внутри вызывается
suspendCoroutine. Это как раз тот волшебный момент, когда корутина говорит: «Всё, стоп. Дайте мне мою бумажку-Continuation, я запишусь». - Тебе в лямбду прилетает этот самый объект
continuation. Ты его не создаёшь руками, система его тебе любезно предоставляет. - Ты запускаешь какую-то асинхронную хрень (
someAsyncTask), которая работает в другом потоке/где угодно, и передаёшь ей колбэк. - Корутина спокойно приостанавливается (
suspend), а поток, на котором она работала, может заняться другими делами. Ничего не блокируется! - Проходит время, асинхронная задача завершается и в колбэк приходит
result. - В этот самый момент ты берёшь сохранённую ранее
continuationи вызываешь у неёresume(result). Это и есть тот самый пинок: «Дружище, просыпайся, вот твой ответ!» - Система хватает эту
continuation, восстанавливает из неё все сохранённые данные, и выполнение корутины продолжается ровно с того места, где остановилось — со строкиreturn suspendCoroutine.... Только теперь это выражение возвращает тот самыйresult, который ты передал вresume.
Вот и вся магия. Никаких потоков, которые жрут память, никаких сложных состояний. Просто умная «бумажка», которая знает, как возобновить работу. Если вдуматься, то гениально и просто, как всё гениальное. Главное — не перепутать и не вызвать resume два раза, а то будет тебе хиросима, корутина ругнётся на всё коромысло.