Coroutine😈

Sümeyra Özuğur🍂
5 min readFeb 28, 2023

Bu konuyu daha iyi anlamak için fonksiyon ve thread kavramlarına öncelikle değinelim.

➡️Fonksiyon: İnput alabilen talimatlar zinciri ve bunun sonuncunda bize çıktılar veren yapı şeklinde tanımlayabiliriz.

➡️Thread: Talimatlar zincirinin hangi context te çalışması gerektiğini belirliyor.

Genelde işlerimizi Main Thread de yapıyoruz. Ama biz Main Thread’i küçük işlemler için kullanmalıyız.

Örnek: Ui etkileşimleri, Buton tıklama, matematik işlemleri, değer atama, matıksal işlemler…

Ya diğer işlemler (network, dosya indirme, görsel yükleme, database işlemleri) içinde main threadı kullansak ne olur?

https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZmUxZWJlZjYwYjYzNjc3MzNlYTRhMTY0ZTBlMDA5ZTg5NTE5YTgwNSZjdD1n/Nx85vtTY70T3W/giphy.gif

Main theread imiz adeta tükenmişlik sendromuna girer ve uygulamamız crash alır.

İşlemleri eş zamanlı olarak gerçekleştirebilmek için multi-thread yapısını kullanmalıyız. Böylece bir thread bir şeyle uğraşıken diğer thread başka bir durumla uğraşabilir.

Hatta bu thread işlemini bitirdikten sonra şu thread çalışacak şeklinde de ayarlayabiliriz.

⭐️⭐️Ama thread ler ile çalışmak mantıklı değil. Threat sayısı arttıkça cpu yoruluyor ve memory sorunlarını oluşturup uygulamanın yine crash olmasına neden oluyor.

İşte tam bu noktada sahneye Coroutine davet edelim. Peki Coroutine bu işlemi nasıl yapıyor?

Sadece bir tane Background Thread(Worker thread) oluşuyor ve coroutine buna üzerine kuruluyor. Coroutine’ın yaptığı şey tam olarak bu.

👀Coroutine’nin Özellikleri:

  • Lightweight :Thread lere göre çok hafif yapılardır.
  • Bir thread in içinde çalışırlar.
  • Thread ler gibi paralel çalışabilirler, birbirlerini bekleyebilir, birbirleri ile iletişim halinde olabilirler.
  • Fewer Memory Leaks : Coroutine’in çalıştığı scope iptal edildiğinde, o scope dahilindeki coroutine’ler de iptal edilir.

Coroutin ler thread DEĞİLDİR.

Coroutin oluşturmak sistemler için çok ucuzdur. Binlercesini oluşturup kullanabilirsiniz herhangi bir memory sorunuylada karşılaşmazsınız.

Coroutineler başlamak için coroutine scope’a ihtiyaç duyar.

👻SCOPE

  • Kelime anlamı kapsam demektir.
  • Coroutine nerede, nasıl çalıştırılacağını ve yaşam döngüsünü belirleyen yapı.

1.Global Scope

  • Eğer coroutine tüm uygulama boyunca çalıştımak istiyorsan, global scope’u kullanmalısın.
  • Default olarak coroutine contex tanımlıdır ama isterseniz, hangi thread üzerinde çalışmak istediğinizi ayarlayabilirsiniz. (coroutine contex kısmını Coroutine Scope’ta detaylı anlatacağım✌🏼)
  • Eğer biz bu scope taki işlemleri iptal etmek istiyorsak; cancel job yapabiliriz.
val TAG = "MainActivity"
Log.w(TAG,"Main program starts: ${Thread.currentThread().name}")
GlobalScope.launch {
Log.w(TAG,"Fake work starts: ${Thread.currentThread().name}")
delay(1000)
Log.w(TAG,"Fake work finished ${Thread.currentThread().name}")
}
Log.w(TAG,"Main program ends: ${Thread.currentThread().name}")

//Output:
//Main program starts: main
// Main program ends: main
// Fake work starts: DefaultDispatcher-worker-1
//Fake work finished DefaultDispatcher-worker-1

Kod Yorumu: Global scope arka planda çalışmaya devam ediyor. Main thread’i blocklamadan.

Global Scope mümkün oldukça tercih edilmemeli!!!

   val job =GlobalScope.launch(Dispatchers.Default) {
repeat(3){
Log.w(TAG,"Coroutine is working...")
delay(1000)
}
}

runBlocking {
job.join()
Log.w(TAG,"Main Thread is working")

}

//Output
//Coroutine is working...
//Coroutine is working...
//Coroutine is working...
//Main Thread is working

Kod Yorumu : job.join() ile GlobalScope taki işlemlerimiz bittikten sonra çalış demiş olduk.

launch: Coroutine başlatma fonksiyonudur. Doğal olarak bu fonksiuonu kullanabilmemiz için coroutine scope içerisinde olmamız gerekiyor.

Job: Launch çalıştırdığımız şeyleri biz job nesnesine eşitleyebiliyoruz. Daha sonra da bu coroutine’ın istersek yaşam döngüsünü cancel methoduyla bitirebiliriz yada bitiminde şunları yap şeklinde ayarlayabiliriz.

2.ViewModel Scope

  • Eğer coroutine viewmodel içinde çalıştırmak istiyorsan, viewmodel scope kullanmalısın.
  • Scope taki işlemleri manuel bir şekilde iptal etmeye gerek yoktur. Viewmodel Scope otomatik bir şekilde yaşam döngüsü bitince iptal edecektir.
class MyViewModel : ViewModel() {

fun doSomething() {
viewModelScope.launch {
// Asynchronous work
delay(1000)
Log.d("MyViewModel", "Work done")
}
}
}

3.LifecycleScope

  • Coroutine activity veya fragmentte kullanmak istiyorsan lifescycle scope kullanmalısın.
  • Scope taki işlemleri manuel bir şekilde iptal etmeye gerek yoktur. Activity veya Fragment Scope otomatik bir şekilde yaşam döngüsü bitince iptal edecektir.
class MyFragment: Fragment() {
...
private fun doSomething(){
lifecycleOwner.lifecycleScope.launch {
// Asynchronous work
}
}
}

4.CoroutineScope

View model ve lifecycle gibi durumların dışında kullanıyorsan, kullanmalısın.

CoroutineScope(Dispatchers.Default).launch {
// Asynchronous work

}

Coroutine context alır. Bunu dispatchers yardımı ile yapar. Böylece hangi thread de çalışması gerektiğini ayarlayabiliriz.

Coroutine context: hangi verilerle birlikte çalışacağını bilmesi.

➡Dispatchers.Default

  • CPU kullanımın yoğun olduğu (görüntü işleme, filtreleme, uzun bir diziyi alfabetik olarak dizme gibi) işlerde kullanılır.

➡ Dispatchers.IO

  • Data işlemleri, database veri yazma okuma işlemlerinde, networking (internetten veri çekmek,yazma…), dosya okuma yazma işlemlerinde kullanılır.

➡ Dispatchers.Main

  • UI ile ilgili yaptığımız işlemler için kullanılır.
  • Coroutine’n içinde ui işlemlerine ihtiyaç duyduğunda kullanılır.

➡ Dispatchers.Unconfined

  • Diğer dispatcherlarden farklı. Herhangi bir thread değiştirmiyor. İçerisinde çalıştırılan threade göre kendini ayarlıyor.
  • Unit test için faydalı

with contex ile dispacher değiştirebiliriz veya hangi dispacher de çalışacağımızı söyleyebiliriz.

      runBlocking {
launch(Dispatchers.IO) {
// Networking..
withContext(Dispatchers.Main) {
// Do something about Main Thread
}
}
}

5.Run Blocking

Asenkron olarak çalıştırmak istediğin zaman kullanmalısın. Coroutine ihtiyacın yoktur ama suspend fonksiyonları çağırmak istiyorsundur.

  • İçerisindeki kodlar bitene kadar sonrasındaki kodların çalıştırılmasını durdurur.
  • Genelde test fonksiyonları için kullanılır.
 val TAG = "MainActivity"
Log.w(TAG,"Main program starts: ${Thread.currentThread().name}")
runBlocking {
launch {
Log.w(TAG, "Fake work starts: ${Thread.currentThread().name}")
delay(1000)
Log.w(TAG, "Fake work finished ${Thread.currentThread().name}")
}
}
Log.w(TAG,"Main program ends: ${Thread.currentThread().name}")

//Output
//Main program starts: main
//Fake work starts: main
//Fake work finished main
//Main program ends: main

Kod Yorumu: Görüldüğü gibi thread de herhangi bir değişme yok ve kod asenkron bir şekilde çalışmadı. Aslında bu kapsamları kaldırsaydıkda aynı şekilde olacaktı. “delay(100)” suspend bir fonksiyon olduğu için coroutine içinde çalışması gerek✌🏼

Suspend Fonksiyon

  • İçerisinde coroutine çalıştırılabilen fonksiyonlardır.
  • ⚡️Suspend fonksiyonlar normal fonksiyonları çağırabilirler fakat normal fonksiyonlar suspend fonksiyonları çağıramazlar.
  suspend fun getProductRealtime(path: String): List<ProductModel> = withContext(Dispatchers.IO) {

val docRef = firebaseFirestore.collection(path).get().await()
val favList = favDao.getFavTitles().orEmpty()
val tempList = arrayListOf<ProductModel>()
docRef.documents.forEach { document ->
tempList.add(
ProductModel(
document.id,
document.get("image") as String,
document.get("title") as String,
document.get("description") as String,// as? String ?: "No description available",
document.get("price") as String,
document.get("quantiles") as String,
favList.contains(document.get("title") as String)
)
)
}

tempList
}

Kod Yorumu: Uzun sürebilecek bir işlem için suspend fonksiyonunu kullandık. “With contex” ile de hangi dispatcher da çalışacağını söyledik.

Async

  • launch gibi asenkron kod yazmak için kullanılır.
  • launch “Job” nesnesi döndürür, async ise “Deferred” nesnesi döndürür.
GlobalScope.launch(Dispatchers.IO){
val num1 = async { networkCall1() }
val num2 = async { networkCall2() }

Log.d(TAG, "Answer 1 is ${num1.await()}")
Log.d(TAG, "Answer 2 is ${num2.await()}")
}

}

suspend fun networkCall2():String {
delay(3000L)
return "Ozugur"
}

suspend fun networkCall1():String {
delay(3000L)
return "Sumeyra"
}

Kod Yorumu: async sayesinde kodun asenkron olarak çalışmasını sağlıyoruz. await ile de sonucu alırız. Böylece thread i bloklamadan atama işlemini yapabilmek için sırası ile bitmesini bekliyoruz.

Bunu “launch” ve “join” kullanarak da gerçekleştirebiliriz ancak “async” ve “await” kullanmak daha uygun bir kullanımdır.⚡️

Bu içeriği ingilizce olarak okumak isterseniz, tıklayın🥳

https://tenor.com/view/thank-you-bow-lisa-kudrow-friends-thanks-gif-21514603

Kaynaklar:

https://kotlinlang.org/docs/coroutines-guide.html

https://www.youtube.com/watch?v=lmRzRKIsn1g&t=4829s

https://www.youtube.com/playlist?list=PLQkwcJG4YTCQcFEPuYGuv54nYai_lwil_

--

--