19-bo‘lim
Korutinlar
suspend funksiyalar, runBlocking, launch va async, tuzilgan parallellik, coroutineScope, withContext va bekor qilish.
Ushbu bo‘lim mundarijasi
Korutin - "yengil oqim". Uni to'xtatib, keyin davom ettirish mumkin va u haqiqiy tizim oqimini band qilmaydi.
Bu Kotlin ning eng mashhur imkoniyatlaridan biri.
Korutinlar tilning o'zida emas, alohida kutubxonada.
Gradle da:
dependencies {
implementation(
"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2"
)
}
Bu bo'limdagi hamma misol shu kutubxona bilan ishga tushirilgan.
Birinchi korutin #
import kotlinx.coroutines.*
suspend fun tayyorla(nom: String, vaqt: Long): String {
delay(vaqt)
return "$nom tayyor"
}
fun main() = runBlocking {
println("Boshlandi")
println(tayyorla("Choy", 100))
println("Tugadi")
}
Boshlandi
Choy tayyor
Tugadi
Uchta yangi tushuncha:
| So'z | Ma'nosi |
|---|---|
suspend | Bu funksiya to'xtab tura oladi |
delay | Kutish, lekin oqimni bloklamasdan |
runBlocking | Oddiy koddan korutin dunyosiga ko'prik |
suspend funksiyalar #
suspend funksiyani faqat boshqa suspend funksiyadan yoki korutin
ichidan chaqirish mumkin:
fun oddiy() {
delay(100) // error: suspension functions can be called only
} // within coroutine body
Bu ataylab: delay bajarilishi uchun uni to'xtatib turadigan muhit
kerak.
delay va Thread.sleep #
import kotlinx.coroutines.*
fun main() = runBlocking {
val boshi = System.currentTimeMillis()
val vazifalar = List(50) {
launch { delay(100) }
}
vazifalar.forEach { it.join() }
val vaqt = System.currentTimeMillis() - boshi
println("50 ta korutin ishga tushdi")
println("Jami 1 soniyadan kammi: ${vaqt < 1000}")
}
50 ta korutin ishga tushdi
Jami 1 soniyadan kammi: true
Ellikta korutin, har biri 100 ms kutdi - lekin jami vaqt 100 ms atrofida, 5 soniya emas.
Har bir tizim oqimi (thread) taxminan 1 MB stek xotirasi oladi. Ming oqim - gigabayt xotira.
Korutin esa oddiy obyekt: bir necha o'nlab bayt. Yuz mingtasini yaratish ham muammo emas.
Thread.sleep(100) oqimni bloklaydi - u boshqa hech narsa qila
olmaydi. delay(100) esa korutinni to'xtatadi va oqim shu vaqtda
boshqa korutinlarni bajaradi.
launch - natija kerak bo'lmasa #
import kotlinx.coroutines.*
fun main() = runBlocking {
println("Boshlandi")
val ish = launch {
delay(100)
println(" Fonda bajarildi")
}
println("Kutmasdan davom etdi")
ish.join()
println("Tugadi")
}
Boshlandi
Kutmasdan davom etdi
Fonda bajarildi
Tugadi
launch korutinni ishga tushiradi va darhol qaytadi. U Job
qaytaradi; join() uning tugashini kutadi.
async - natija kerak bo'lsa #
import kotlinx.coroutines.*
suspend fun malumot(nom: String, vaqt: Long): String {
delay(vaqt)
return nom
}
fun main() = runBlocking {
val boshi = System.currentTimeMillis()
val a = async { malumot("birinchi", 200) }
val b = async { malumot("ikkinchi", 200) }
val c = async { malumot("uchinchi", 200) }
println("${a.await()}, ${b.await()}, ${c.await()}")
val vaqt = System.currentTimeMillis() - boshi
println("400 ms dan kammi: ${vaqt < 400}")
}
birinchi, ikkinchi, uchinchi
400 ms dan kammi: true
Uchta ish, har biri 200 ms - lekin jami 200 ms atrofida, chunki ular birga bajarildi.
Chiqishda 205 ms kabi aniq son emas, true yozilgan.
Aniq vaqt kompyuterga va yuklamaga bog'liq - bugun 205, ertaga 240 bo'lishi mumkin. Shuning uchun darslikda chegara solishtiriladi.
O'z kodingizda ham vaqtga bog'liq tekshiruvlarni shunday yozing.
Ketma-ket va parallel #
import kotlinx.coroutines.*
suspend fun ish(vaqt: Long): Int {
delay(vaqt)
return 1
}
fun main() = runBlocking {
val b1 = System.currentTimeMillis()
val ketmaKet = ish(150) + ish(150)
val v1 = System.currentTimeMillis() - b1
val b2 = System.currentTimeMillis()
val a = async { ish(150) }
val b = async { ish(150) }
val parallel = a.await() + b.await()
val v2 = System.currentTimeMillis() - b2
println("Natijalar: $ketmaKet va $parallel")
println("Ketma-ket 250 ms dan ko'pmi: ${v1 > 250}")
println("Parallel 250 ms dan kammi: ${v2 < 250}")
}
Natijalar: 2 va 2
Ketma-ket 250 ms dan ko'pmi: true
Parallel 250 ms dan kammi: true
Farq aniq: suspend funksiyani to'g'ridan-to'g'ri chaqirsangiz -
navbatda bajariladi. async bilan o'rasangiz - birga.
Tuzilgan parallellik #
import kotlinx.coroutines.*
suspend fun hisobla(): Int = coroutineScope {
val a = async { delay(100); 10 }
val b = async { delay(100); 20 }
a.await() + b.await()
}
fun main() = runBlocking {
println("Natija: ${hisobla()}")
println("Hamma ichki korutin tugagan")
}
Natija: 30
Hamma ichki korutin tugagan
coroutineScope blokidan chiqish uchun ichidagi hamma korutin
tugashi shart.
Bu juda muhim kafolat: "unutilgan" korutin qolib ketmaydi. Funksiya qaytdi degani - u boshlagan hamma ish tugagan degani.
Boshqa tillardagi fon vazifalarida bu muammo doimiy: qaysi vazifa hali ishlayotganini kuzatib borish kerak. Kotlin da esa buni til tuzilishi hal qiladi.
Xatolar #
import kotlinx.coroutines.*
suspend fun xavfli(muvaffaqiyat: Boolean): Int {
delay(50)
if (!muvaffaqiyat) throw IllegalStateException("ish bajarilmadi")
return 42
}
fun main() = runBlocking {
try {
println(xavfli(true))
println(xavfli(false))
} catch (e: IllegalStateException) {
println("Ushlandi: ${e.message}")
}
val natija = runCatching {
coroutineScope {
val a = async { xavfli(true) }
val b = async { xavfli(false) }
a.await() + b.await()
}
}
println("Muvaffaqiyatmi: ${natija.isSuccess}")
}
42
Ushlandi: ish bajarilmadi
Muvaffaqiyatmi: false
Bitta async xato bersa, coroutineScope butun blokni bekor
qiladi va xatoni yuqoriga uzatadi. Qolgan korutinlar ham to'xtaydi -
bu ham tuzilgan parallellikning bir qismi.
Bekor qilish #
import kotlinx.coroutines.*
fun main() = runBlocking {
val ish = launch {
repeat(100) { i ->
println(" qadam $i")
delay(50)
}
}
delay(120)
ish.cancel()
ish.join()
println("Bekor qilindi")
println("Ish tugadimi: ${ish.isCompleted}")
}
qadam 0
qadam 1
qadam 2
Bekor qilindi
Ish tugadimi: true
cancel() korutinni to'xtatadi. delay kabi suspend funksiyalar
bekor qilishni o'zi tekshiradi - shuning uchun sikl uchinchi
qadamda to'xtadi.
Agar korutin ichida suspend funksiya bo'lmasa - masalan uzun
matematik sikl - cancel() unga ta'sir qilmaydi.
Bunday holatda vaqti-vaqti bilan yield() chaqiring yoki
isActive ni tekshiring:
while (isActive) {
// hisoblash
}
withContext #
import kotlinx.coroutines.*
suspend fun ogirIsh(): Long = withContext(Dispatchers.Default) {
var jami = 0L
for (i in 1..1_000_000) jami += i
jami
}
fun main() = runBlocking {
println(ogirIsh())
println("Asosiy korutin bloklanmadi")
}
500000500000
Asosiy korutin bloklanmadi
Diqqat: jami Long qilib olingan. Int bo'lganda natija
1784293664 chiqardi - 3-bo'limdagi to'lib ketish, bu safar amaliy
misolda.
withContext ishni boshqa dispetcherga ko'chiradi:
| Dispetcher | Nima uchun |
|---|---|
Dispatchers.Default | Protsessorga og'ir hisoblash |
Dispatchers.IO | Fayl, tarmoq, ma'lumotlar bazasi |
Dispatchers.Main | Android interfeysi |
Amaliy misol #
import kotlinx.coroutines.*
data class Natija(val manba: String, val soni: Int)
suspend fun yukla(manba: String, vaqt: Long, soni: Int): Natija {
delay(vaqt)
return Natija(manba, soni)
}
fun main() = runBlocking {
val boshi = System.currentTimeMillis()
val natijalar = listOf(
async { yukla("talabalar", 150, 120) },
async { yukla("darsliklar", 100, 22) },
async { yukla("testlar", 120, 330) }
).awaitAll()
val vaqt = System.currentTimeMillis() - boshi
natijalar.forEach { println("${it.manba}: ${it.soni}") }
println("Jami yozuv: ${natijalar.sumOf { it.soni }}")
println("300 ms dan kammi: ${vaqt < 300}")
}
talabalar: 120
darsliklar: 22
testlar: 330
Jami yozuv: 472
300 ms dan kammi: true
awaitAll() - hamma async natijasini birdan kutadi va ularni
berilgan tartibda qaytaradi.
Internetda GlobalScope.launch { } ko'p uchraydi. Undan qoching.
GlobalScope da ishga tushirilgan korutin hech kimga bog'lanmaydi:
uni hech kim kutmaydi, bekor qilmaydi va xatosini ushlamaydi. Bu
aynan tuzilgan parallellik hal qiladigan muammoni qaytarib keltiradi.
O'rniga coroutineScope, runBlocking yoki o'z CoroutineScope
ingizni ishlating.
- 100 ms kutib, matn qaytaradigan
suspendfunksiya yozing. - Uni
runBlockingichidan chaqiring. suspendfunksiyani oddiy funksiyadan chaqirib, xatoni o'qing.launchbilan fon vazifasi ishga tushiring vajoin()qiling.- Uchta ishni
asyncbilan parallel bajaring. - Ketma-ket va parallel vaqtni o'lchab solishtiring.
coroutineScopebilan tuzilgan parallellikni ko'rsating.- Bitta
asyncda xato ko'tarib, qolganiga ta'sirini kuzating. cancel()bilan uzoq ishni to'xtating.withContext(Dispatchers.Default)bilan og'ir hisoblash bajaring.
Xulosa #
suspendfunksiya to'xtab tura oladi; uni faqat korutin ichidan chaqirish mumkin.delayoqimni bloklamaydi -Thread.sleepesa bloklaydi.- Korutin oqimdan ming marta arzon: yuz mingtasini yaratish mumkin.
launch- natija kerak bo'lmasa,async/await- kerak bo'lsa.suspendfunksiyani to'g'ridan-to'g'ri chaqirish ketma-ket,asyncbilan o'rash esa parallel bajaradi.coroutineScopeichidagi hamma korutin tugamaguncha qaytmaydi - tuzilgan parallellik.- Bitta korutindagi xato butun qamrovni bekor qiladi.
cancel()suspendnuqtalarida ishlaydi; hisoblash siklidaisActivetekshiring.GlobalScopedan foydalanmang.
Keyingi bo'limda hamma narsani birlashtirib, to'liq dastur yozamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.