За счет чего корутину можно возобновлять

Ответ

Корутину можно возобновлять благодаря механизму 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)
        }
    }
}

Что тут происходит, по шагам:

  1. Ты вызываешь fetchData(). Это suspend-функция, значит, её можно приостановить.
  2. Внутри вызывается suspendCoroutine. Это как раз тот волшебный момент, когда корутина говорит: «Всё, стоп. Дайте мне мою бумажку-Continuation, я запишусь».
  3. Тебе в лямбду прилетает этот самый объект continuation. Ты его не создаёшь руками, система его тебе любезно предоставляет.
  4. Ты запускаешь какую-то асинхронную хрень (someAsyncTask), которая работает в другом потоке/где угодно, и передаёшь ей колбэк.
  5. Корутина спокойно приостанавливается (suspend), а поток, на котором она работала, может заняться другими делами. Ничего не блокируется!
  6. Проходит время, асинхронная задача завершается и в колбэк приходит result.
  7. В этот самый момент ты берёшь сохранённую ранее continuation и вызываешь у неё resume(result). Это и есть тот самый пинок: «Дружище, просыпайся, вот твой ответ!»
  8. Система хватает эту continuation, восстанавливает из неё все сохранённые данные, и выполнение корутины продолжается ровно с того места, где остановилось — со строки return suspendCoroutine.... Только теперь это выражение возвращает тот самый result, который ты передал в resume.

Вот и вся магия. Никаких потоков, которые жрут память, никаких сложных состояний. Просто умная «бумажка», которая знает, как возобновить работу. Если вдуматься, то гениально и просто, как всё гениальное. Главное — не перепутать и не вызвать resume два раза, а то будет тебе хиросима, корутина ругнётся на всё коромысло.