Боль: Корутины в Kotlin
Публичный постПринято считать, что Kotlin - очень простой язык. Но в Котлине есть корутины.
Корутины мне лично не даются. Сначала разбирался по книжкам, но в книгах не шли дальше тривиальных примеров. Как писать с корутинами? Как правильно?
Я перечитал официальную документацию, покиберсталкал Романа Елизарова в твиттере и на медиуме, полез на душный Хабр. Примеров мало. Очень мало.
Хочется где-то собрать примеры, сэмплы, паттерны, интересные факты, решения типовых задач на корутинах, возможно какие-то особо интересные статьи и книги, ссылки на stackoverflow, все что угодно. Но предпочтительнее, чтобы там был код какой-то, теории в интернете навалом, та же документация в целом ничего.
В общем в этом треде поделиться реальным опытом, чтобы натренировать свое подсознание. Чтобы появилась некая интуиция в корутинах. Какие примеры? Какие угодно, которые вы понимаете и считаете интересными
Скину несколько примеров:
- Если мы идем по блоку suspend-функций и упираемся в долгий блокирующий вызов, то правильно его оборачивать в withContext.
Пусть у нас есть функция:
fun findBigPrime() : BigInteger =
BigInteger.probablePrime(4096, Random())
Правильно будет вызвать ее так:
suspend fun findBigPrime(): BigInteger =
withContext(Dispatchers.Default) {
BigInteger.probablePrime(4096, Random())
}
Этот пример также иллюстрирует необходимость разделять вычисления, блокирующие IO от вычислений, нагружающих процессор. В корутинах это соответственно делается за счет использования диспетчеров Dispatchers.Default и Dispatchers.IO
- coroutineScope vs withContext
coroutineScope == withContext(this.coroutineContext)
- runBlocking vs coroutineScope
runBlocking - запускает корутину и блокирует вызывающий тред, пока корутина не закончится. runBlocking - не suspend-функция.
coroutineScope - suspend-функция. Если вызывающая корутина суспендится, то coroutineScope точно также засуспендится, что в свою очередь освободит вызывающий тред.
У меня получилось сделать пример где явно видна разница между coroutineScope и runBlocking, но он достаточно корявый
suspend fun main() {
val elapsedTime = measureTimeMillis {
withContext(newSingleThreadContext("single")) {
launch { delay(1000); println("child 0 is finished")}
coroutineScope {
launch { delay(1000); println("child 1 is finihed") }
launch { delay(1000); println("child 2 is finished") }
}
}
}
println("Main completed in $elapsedTime")
}
Выполняется у меня за 1200 мс.
Тогда как:
suspend fun main() {
val elapsedTime = measureTimeMillis {
withContext(newSingleThreadContext("single")) {
launch { delay(1000); println("child 0 is finished")}
runBlocking {
launch { delay(1000); println("child 1 is finihed") }
launch { delay(1000); println("child 2 is finished") }
}
}
}
println("Main completed in $elapsedTime")
}
Отрабатывает за 2200 мс.
- GlobalScope in runBlocking
Если запустить GlobalScope внутри runBlocking, то runBlocking не будет дожидаться исполнения GlobalScope.launch. GlobalScope создает "глобальные корутины", которые не являются дочерними от runBlocking
Такой код ничего не выведет:
fun main() {
runBlocking {
for (i in 1..2) {
GlobalScope.launch {
Thread.sleep(1000)
println("work $i done")
}
}
}
}
Логику работы с корутинами можно помещать в extension-функции CoroutineScope. Хотя я, например, когда надо, когда не надо. И все же действует контракт - такая функция запускает параллельно работу в фоне, и немедленно возвращается.
fun CoroutineScope.launchFoo() = launch {
delay(1000)
println("foo")
}
Поэтому ни в коем случае не надо писать suspend extension-функции:
suspend fun CoroutineScope.foo() = ???
За пост – спасибо. Сам испытываю похожие затруднения: непонятно, мало примеров, не хватает интуиции.
Как справиться?
suspend
-функции. А уже потом разобраться с тонкостями контекста и скоупа,launch
,async
,coroutineScope
. Рано или поздно выработается интуиция.у меня был опыт с корутинами год назад примерно
так что выпилил я это все из проекта и стало спокойнее и проще. Сейчас может что-то за год поменялось, я не следил