19-bo‘lim

Korutinlar

suspend funksiyalar, runBlocking, launch va async, tuzilgan parallellik, coroutineScope, withContext va bekor qilish.

🕑 14 daqiqa o‘qish 📄 711 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Birinchi korutin
  2. suspend funksiyalar
  3. delay va Thread.sleep
  4. launch - natija kerak bo'lmasa
  5. async - natija kerak bo'lsa
  6. Ketma-ket va parallel
  7. Tuzilgan parallellik
  8. Xatolar
  9. Bekor qilish
  10. withContext
  11. Amaliy misol
  12. Xulosa

Korutin - "yengil oqim". Uni to'xtatib, keyin davom ettirish mumkin va u haqiqiy tizim oqimini band qilmaydi.

Bu Kotlin ning eng mashhur imkoniyatlaridan biri.

Qo'shimcha kutubxona kerak

Korutinlar tilning o'zida emas, alohida kutubxonada.

Gradle da:

KOTLIN
dependencies {
    implementation(
        "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2"
    )
}

Bu bo'limdagi hamma misol shu kutubxona bilan ishga tushirilgan.

Birinchi korutin #

KOTLIN
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")
}
Natija
Boshlandi
Choy tayyor
Tugadi

Uchta yangi tushuncha:

So'zMa'nosi
suspendBu funksiya to'xtab tura oladi
delayKutish, lekin oqimni bloklamasdan
runBlockingOddiy koddan korutin dunyosiga ko'prik

suspend funksiyalar #

suspend funksiyani faqat boshqa suspend funksiyadan yoki korutin ichidan chaqirish mumkin:

KOTLIN
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 #

KOTLIN
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}")
}
Natija
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.

Nega Thread dan arzon

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 #

KOTLIN
import kotlinx.coroutines.*

fun main() = runBlocking {
    println("Boshlandi")

    val ish = launch {
        delay(100)
        println("  Fonda bajarildi")
    }

    println("Kutmasdan davom etdi")
    ish.join()
    println("Tugadi")
}
Natija
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 #

KOTLIN
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}")
}
Natija
birinchi, ikkinchi, uchinchi
400 ms dan kammi: true

Uchta ish, har biri 200 ms - lekin jami 200 ms atrofida, chunki ular birga bajarildi.

Nega aniq vaqt yozilmadi

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 #

KOTLIN
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}")
}
Natija
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 #

KOTLIN
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
Natija: 30
Hamma ichki korutin tugagan
Tuzilgan parallellik - korutinlarning asosiy g'oyasi

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 #

KOTLIN
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}")
}
Natija
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 #

KOTLIN
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}")
}
Natija
  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.

Hisoblash sikli bekor qilinmaydi

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:

KOTLIN
while (isActive) {
    // hisoblash
}

withContext #

KOTLIN
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")
}
Natija
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:

DispetcherNima uchun
Dispatchers.DefaultProtsessorga og'ir hisoblash
Dispatchers.IOFayl, tarmoq, ma'lumotlar bazasi
Dispatchers.MainAndroid interfeysi

Amaliy misol #

KOTLIN
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}")
}
Natija
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.

GlobalScope dan foydalanmang

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.

Amaliy topshiriq
  1. 100 ms kutib, matn qaytaradigan suspend funksiya yozing.
  2. Uni runBlocking ichidan chaqiring.
  3. suspend funksiyani oddiy funksiyadan chaqirib, xatoni o'qing.
  4. launch bilan fon vazifasi ishga tushiring va join() qiling.
  5. Uchta ishni async bilan parallel bajaring.
  6. Ketma-ket va parallel vaqtni o'lchab solishtiring.
  7. coroutineScope bilan tuzilgan parallellikni ko'rsating.
  8. Bitta async da xato ko'tarib, qolganiga ta'sirini kuzating.
  9. cancel() bilan uzoq ishni to'xtating.
  10. withContext(Dispatchers.Default) bilan og'ir hisoblash bajaring.

Xulosa #

  • suspend funksiya to'xtab tura oladi; uni faqat korutin ichidan chaqirish mumkin.
  • delay oqimni bloklamaydi - Thread.sleep esa bloklaydi.
  • Korutin oqimdan ming marta arzon: yuz mingtasini yaratish mumkin.
  • launch - natija kerak bo'lmasa, async/await - kerak bo'lsa.
  • suspend funksiyani to'g'ridan-to'g'ri chaqirish ketma-ket, async bilan o'rash esa parallel bajaradi.
  • coroutineScope ichidagi hamma korutin tugamaguncha qaytmaydi - tuzilgan parallellik.
  • Bitta korutindagi xato butun qamrovni bekor qiladi.
  • cancel() suspend nuqtalarida ishlaydi; hisoblash siklida isActive tekshiring.
  • GlobalScope dan foydalanmang.

Keyingi bo'limda hamma narsani birlashtirib, to'liq dastur yozamiz.

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.