serhattastan.com
/
← Yazılar

5 Nisan 2024·4 dk okuma

Kotlin Coroutines Üzerine

Kotlin Coroutines Üzerine

Neden Coroutines?

Asenkron programlama, kullanıcı arayüzünü bloklamadan uzun süreli işlemleri gerçekleştirmemizi sağlar. Android uygulamalarında, ağ istekleri veya veri tabanı işlemleri gibi uzun sürebilecek işlemleri asenkron olarak yapmamız gerekir. Callback’ler ve RxJava gibi diğer asenkron programlama yöntemleri genellikle karmaşık ve okunması zor kodlara yol açabilir. Kotlin Coroutines, bu sorunları çözmek için tasarlanmıştır ve basit, sade bir API ile güçlü bir asenkron programlama modeli sunar.

Coroutine Temelleri

Coroutines, askıya alınabilen ve devam ettirilebilen fonksiyonlar etrafında döner. suspend anahtar kelimesi, bir fonksiyonun askıya alınabileceğini ve daha sonra devam ettirilebileceğini belirtir.

Basit bir örnekle başlayalım:

import kotlinx.coroutines.*
fun main() = runBlocking {
    launch {
        delay(1000L)
        println("Dünya")
    }
    println("Merhaba")
}

Bu kodun çıktısı sırasıyla “Merhaba” ve “Dünya” olacaktır. runBlocking, bir coroutine başlatır ve içindeki tüm coroutine’ler tamamlanana kadar bloklanır. launch , yeni bir coroutine başlatır. , coroutine’i belirtilen süre boyunca askıya alır.

CoroutineScope ve Coroutine Builder’lar

Coroutines, belirli bir scope içinde çalıştırılır. CoroutineScope ile coroutine’leri başlatır ve yönetiriz. En yaygın kullanılan coroutine builder’lar şunlardır:

  • launch: Bir coroutine başlatır ve Job döner.

  • async: Bir coroutine başlatır ve Deferred döner, await ile sonucunu alabiliriz.

  • runBlocking: Mevcut thread’i bloklayarak bir coroutine başlatır.

Örnek:

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        delay(1000L)
        println("Coroutine tamamlandı.")
    }
    println("Main thread çalışıyor.")
    job.join()  // Coroutine tamamlanana kadar bekle
    println("Main thread tamamlandı.")
}

Suspend Fonksiyonlar

Suspend fonksiyonlar, coroutine içinde kullanılabilen ve başka bir coroutine’i askıya alabilen fonksiyonlardır. Bu, uzun sürebilecek işlemleri kolayca yönetmemizi sağlar.

Örnek:

suspend fun fetchData(): String {
    delay(1000L) // Simulated network request
    return "Data fetched"
}
fun main() = runBlocking {
    val data = fetchData()
    println(data)
}

Burada fetchData fonksiyonu, bir ağ isteği simülasyonu yapar ve 1 saniye bekledikten sonra bir veri döner.

Dispatchers ile Çalışmak

Coroutines, farklı thread’lerde çalıştırılabilir. Dispatchers ile hangi thread’de çalışacağını belirtebiliriz:

  • Dispatchers.Default: CPU yoğun işler için kullanılır.

  • Dispatchers.IO: Giriş/Çıkış işlemleri için kullanılır.

  • Dispatchers.Main: Ana thread’de çalışmak için kullanılır (genellikle Android’de kullanılır).

Örnek:

import kotlinx.coroutines.*
fun main() = runBlocking {
    launch(Dispatchers.Default) {
        println("Default Dispatcher: ${Thread.currentThread().name}")
    }
    launch(Dispatchers.IO) {
        println("IO Dispatcher: ${Thread.currentThread().name}")
    }
    launch(Dispatchers.Main) {
        println("Main Dispatcher: ${Thread.currentThread().name}")
    }
}

CoroutineScope ve SupervisorScope

CoroutineScope, coroutine’leri yönetmek için kullanılır. launch ve async gibi coroutine başlatan fonksiyonlar, belirli bir scope içinde çalıştırılır. SupervisorScope ise hata yönetimi için kullanışlıdır.

Örnek:

fun main() = runBlocking {
    val scope = CoroutineScope(Dispatchers.Default)
    scope.launch {
        try {
            val data = fetchData()
            println(data)
        } catch (e: Exception) {
            println("Error: ${e.message}")
        }
    }
    println("Fetching data...")
}

CoroutineScope ile coroutine’leri başlatıp yönetebiliriz. Eğer bir coroutine hata verirse, diğer coroutine’ler etkilenmez ve çalışmaya devam eder.

Structured Concurrency (Yapısal Eşzamanlılık)

Coroutines, yapısal eşzamanlılık ilkesine dayanır. Bu, coroutine’lerin bir hiyerarşi içinde düzenlendiği ve üst coroutine’in alt coroutine’lerin tamamlanmasını beklediği anlamına gelir.

Örnek:

fun main() = runBlocking {
    launch {
        supervisorScope {
            val job1 = launch {
                delay(1000L)
                println("Job 1 tamamlandı.")
            }
            val job2 = launch {
                delay(500L)
                println("Job 2 tamamlandı.")
            }
        }
        println("Supervisor scope tamamlandı.")
    }
    println("Main thread çalışıyor.")
}

Burada supervisorScope kullanarak, bir coroutine’de hata oluştuğunda diğer coroutine’lerin etkilenmemesini sağlıyoruz.

Flow API

Flow, Kotlin Coroutines içinde yer alan bir veri akışı yapısıdır ve reaktif programlama için kullanılır. Flow, veri akışlarını yönetmek ve işlemek için güçlü bir araçtır.

Örnek:

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun simpleFlow(): Flow<Int> = flow {
    for (i in 1..3) {
        delay(100L)
        emit(i)
    }
}fun main() = runBlocking {
    simpleFlow().collect { value ->
        println(value)
    }
}

Bu örnekte, simpleFlow fonksiyonu bir Flow oluşturur ve 1, 2, 3 değerlerini 100 milisaniye aralıklarla yayar.

Android Uygulamalarında Coroutines Kullanımı

Android geliştirmede, Coroutines kullanarak asenkron işlemleri yönetmek oldukça yaygındır. Aşağıda, bir ağ isteği ve veri tabanı işlemi gibi yaygın senaryolar için Coroutine kullanımına örnekler verelim.

Ağ İsteği:

import kotlinx.coroutines.*
import okhttp3.OkHttpClient
import okhttp3.Request
suspend fun fetchApiData(): String {
    val client = OkHttpClient()
    val request = Request.Builder().url("https://api.example.com/data").build()
    return withContext(Dispatchers.IO) {
        client.newCall(request).execute().body?.string() ?: "Error"
    }
}fun main() = runBlocking {
    val data = fetchApiData()
    println(data)
}

Veri Tabanı İşlemi:

import kotlinx.coroutines.*
import androidx.room.*
@Entity
data class User(
    @PrimaryKey val id: Int,
    val name: String
)@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    suspend fun getAll(): List<User>    @Insert
    suspend fun insertAll(vararg users: User)
}@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}fun main() = runBlocking {
    val db = Room.databaseBuilder(
        applicationContext,
        AppDatabase::class.java, "database-name"
    ).build()    val userDao = db.userDao()    launch(Dispatchers.IO) {
        val users = userDao.getAll()
        println(users)
    }
}

Kotlin Coroutines, asenkron programlamayı daha kolay, okunabilir ve performanslı hale getirir. suspend fonksiyonlar, Dispatchers, CoroutineScope, Flow gibi kavramlar ile güçlü ve esnek bir yapı sunar. Asenkron işlemleri yönetmek ve kullanıcı deneyimini artırmak için Coroutines, modern Android geliştirmenin vazgeçilmez bir parçasıdır.

Umarım bu yazım, Kotlin Coroutines hakkında daha iyi bir anlayış sağlar. İyi kodlamalar!

Serhat Taştan

Yazan

Serhat Taştan

Mobile & Web Developer & Trainer & CATH Lab Nurse

Mühendislik disiplinine sahip bir yazılım geliştirici ve Başakşehir Çam ve Sakura Şehir Hastanesi'nde (Anjiyo Ünitesi) görev yapan bir hemşireyim. Unvanlara takılmadan, klinik deneyimimi ve yazılım yeteneklerimi birleştirerek problem odaklı çözümler üretiyorum. Çok yönlülüğün gücüne inanarak, ister hastanede ister klavye başında süreçleri iyileştiriyor ve somut sonuçlar yaratıyorum.