Kotlin eş yordamlarıyla uygulama performansını iyileştirme

Kotlin eş yordamları, ağ çağrıları veya disk işlemleri gibi uzun süreli görevleri yönetirken uygulamanızın duyarlı olmasını sağlayan net ve basitleştirilmiş eşzamansız kod yazmanıza olanak tanır.

Bu konuda, Android'deki eş yordamlar ayrıntılı bir şekilde açıklanmaktadır. Eş yordamlar hakkında bilginiz yoksa bu konuyu okumadan önce Android'deki Kotlin eş yordamlarını okuduğunuzdan emin olun.

Uzun süreli görevleri yönetin

İşbirlikleri, uzun süreli görevleri yerine getirmek için iki işlem ekleyerek normal işlevler üzerine kuruludur. invoke (veya call) ve return'ye ek olarak eş yordalar suspend ve resume ekler:

  • suspend, mevcut eş yordasının yürütülmesini duraklatarak tüm yerel değişkenleri kaydeder.
  • resume, askıya alınmış bir eş yordamı askıya alındığı yerden yürütülmeye devam ediyor.

suspend işlevlerini yalnızca diğer suspend işlevlerinden veya yeni bir eş yordam başlatmak için launch gibi bir eş yordam oluşturucu kullanarak çağırabilirsiniz.

Aşağıdaki örnekte, uzun süreli bir görev için basit bir eş yordam uygulaması gösterilmektedir:

suspend fun fetchDocs() {                             // Dispatchers.Main
    val result = get("https://developer.android.com") // Dispatchers.IO for `get`
    show(result)                                      // Dispatchers.Main
}

suspend fun get(url: String) = withContext(Dispatchers.IO) { /* ... */ }

Bu örnekte get(), ana iş parçacığında çalışmaya devam eder ancak ağ isteğini başlatmadan önce eş yordanı askıya alır. Ağ isteği tamamlandığında get, ana iş parçacığını bildirmek için bir geri çağırma kullanmak yerine askıya alınmış eş yordasını devam ettirir.

Kotlin, yerel değişkenlerle birlikte hangi işlevin çalıştığını yönetmek için bir yığın çerçevesi kullanır. Bir eş yordamı askıya alırken mevcut yığın çerçevesi kopyalanır ve daha sonra kullanılmak üzere kaydedilir. Devam ettirildiğinde, yığın çerçevesi kaydedildiği yerden tekrar kopyalanır ve işlev tekrar çalışmaya başlar. Kod sıradan bir sıralı engelleme isteği gibi görünse de eş yordam, ağ isteğinin ana iş parçacığını engellemekten kaçınmasını sağlar.

Ana güvenlik için eş yordamları kullan

Kotlin eş yordamları, eş yordam yürütmesi için hangi iş parçacıklarının kullanıldığını belirlemek amacıyla dispatcher'ları kullanır. Kodu ana iş parçacığının dışında çalıştırmak için Kotlin eş yordamlarına Varsayılan veya IO görev dağıtıcıda çalışma yapmasını bildirebilirsiniz. Kotlin'de tüm eş yordamlar, ana iş parçacığında çalışırken bile bir görev dağıtıcıda çalışmalıdır. İşbirlikleri kendilerini askıya alabilir ve bunları devam ettirmekten görev dağıtıcı sorumlu olur.

Kotlin'in nerede çalışması gerektiğini belirlemek için Kotlin, kullanabileceğiniz üç görev dağıtıcı sağlar:

  • Dispatchers.Main - Ana Android iş parçacığında bir eş yordam çalıştırmak için bu görev dağıtıcıyı kullanın. Bu, yalnızca kullanıcı arayüzüyle etkileşim kurmak ve hızlı çalışma gerçekleştirmek için kullanılmalıdır. Örnekler arasında suspend işlevlerini çağırma, Android kullanıcı arayüzü çerçeve işlemlerini çalıştırma ve LiveData nesnelerini güncelleme yer alır.
  • Dispatchers.IO - Bu görev dağıtıcı, ana iş parçacığının dışında disk veya ağ G/Ç'si gerçekleştirmek için optimize edilmiştir. Örnekler arasında Oda bileşenini kullanma, dosyalardan okuma veya dosyalara yazma ve tüm ağ işlemlerini çalıştırma yer alır.
  • Dispatchers.Default - Bu görev dağıtıcı, ana iş parçacığının dışında CPU yoğun çalışmalar gerçekleştirmek üzere optimize edilmiştir. Örnek kullanım alanları arasında bir listeyi sıralama ve JSON'u ayrıştırma yer alır.

Önceki örnekten devam edersek, get işlevini yeniden tanımlamak için görev dağıtıcıları kullanabilirsiniz. IO iş parçacığı havuzunda çalışan bir blok oluşturmak için get gövdesinde withContext(Dispatchers.IO) çağrısı yapın. Bu blokun içine yerleştirdiğiniz tüm kodlar her zaman IO görev dağıtıcısı aracılığıyla yürütülür. withContext işlevinin kendisi bir askıya alma işlevi olduğundan get işlevi de bir askıya alma işlevidir.

suspend fun fetchDocs() {                      // Dispatchers.Main
    val result = get("developer.android.com")  // Dispatchers.Main
    show(result)                               // Dispatchers.Main
}

suspend fun get(url: String) =                 // Dispatchers.Main
    withContext(Dispatchers.IO) {              // Dispatchers.IO (main-safety block)
        /* perform network IO here */          // Dispatchers.IO (main-safety block)
    }                                          // Dispatchers.Main
}

Eş yordamlar sayesinde iş parçacıklarını ayrıntılı bir denetimle dağıtabilirsiniz. withContext(), geri çağırma olmaksızın herhangi bir kod satırının ileti dizisi havuzunu kontrol etmenize olanak sağladığından, bu kodu veritabanından okuma veya ağ isteği gerçekleştirme gibi çok küçük işlevlere uygulayabilirsiniz. Her işlevin main güvenli olduğundan emin olmak için withContext() kullanmanız iyi bir uygulamadır. Diğer bir deyişle, işlevi ana iş parçacığından çağırabilirsiniz. Bu şekilde, çağrıyı yapan kişinin işlevi yürütmek için hangi iş parçacığının kullanılması gerektiğini asla düşünmesine gerek kalmaz.

Önceki örnekte fetchDocs(), ana iş parçacığında yürütülür. Ancak arka planda ağ isteği gerçekleştiren get işlemini güvenli bir şekilde çağırabilir. Eş yordamlar suspend ve resume özelliklerini desteklediğinden ana iş parçacığındaki eş yordam, withContext bloğu biter bitmez get sonucuyla devam ettirilir.

withContext() performansı

withContext(), geri çağırmaya dayalı eşdeğer bir uygulamaya kıyasla fazladan ek yük sağlamaz. Ayrıca bazı durumlarda withContext() çağrılarını, geri çağırmaya dayalı eşdeğer bir uygulamanın ötesinde optimize etmek mümkündür. Örneğin, bir işlev bir ağa on çağrı yapıyorsa Kotlin'e, dış bir withContext() kullanarak iş parçacıklarını yalnızca bir kez değiştirmesini söyleyebilirsiniz. Ardından, ağ kitaplığı withContext() hizmetini birden çok kez kullansa bile aynı dağıtıcıda kalır ve iş parçacıkları arasında geçiş yapmaktan kaçınır. Buna ek olarak Kotlin, mümkün olduğunda iş parçacığı geçişlerini önlemek için Dispatchers.Default ile Dispatchers.IO arasında geçişi optimize eder.

Eş yordam başlat

Eş yordamları şu iki yöntemden biriyle başlatabilirsiniz:

  • launch yeni bir eş yordam başlatır ve sonucu arayana döndürmez. "Ateş ve unut" olarak kabul edilen tüm çalışmalar launch kullanılarak başlatılabilir.
  • async yeni bir eş yordam başlatır ve await adlı askıya alma işleviyle sonuç döndürmenize olanak tanır.

Normal bir işlev await çağıramaz. Bu nedenle, genellikle normal işlevden yeni bir eş yordamı launch yapmanız gerekir. async öğesini yalnızca başka bir eş yordamın içindeyken veya bir askıya alma işlevinin içindeyken ve paralel ayrıştırma gerçekleştirirken kullanın.

Paralel ayrıştırma

Bir suspend işlevi içinde başlatılan tüm eş yordamlar, bu işlev geri döndüğünde durdurulmalıdır. Bu nedenle, geri dönmeden önce bu eş yordamların bittiğini garanti etmeniz gerekir. Kotlin'de yapılandırılmış eşzamanlılık ile bir veya daha fazla eş yordam başlatan bir coroutineScope tanımlayabilirsiniz. Daha sonra, await() (tek bir eş yordam için) veya awaitAll() (birden çok eş yordam için) kullanarak bu eş yordamlarının işlevden dönmeden önce bittiğini garanti edebilirsiniz.

Örnek olarak, iki belgeyi eşzamansız olarak getiren bir coroutineScope tanımlayalım. Ertelenen her referansta await() yöntemini çağırarak her iki async işleminin de değer döndürmeden önce tamamlanmasını garanti ederiz:

suspend fun fetchTwoDocs() =
    coroutineScope {
        val deferredOne = async { fetchDoc(1) }
        val deferredTwo = async { fetchDoc(2) }
        deferredOne.await()
        deferredTwo.await()
    }

awaitAll() öğesini koleksiyonlarda da aşağıdaki örnekte gösterildiği gibi kullanabilirsiniz:

suspend fun fetchTwoDocs() =        // called on any Dispatcher (any thread, possibly Main)
    coroutineScope {
        val deferreds = listOf(     // fetch two docs at the same time
            async { fetchDoc(1) },  // async returns a result for the first doc
            async { fetchDoc(2) }   // async returns a result for the second doc
        )
        deferreds.awaitAll()        // use awaitAll to wait for both network requests
    }

fetchTwoDocs(), async ile yeni eş yordamları başlatsa bile işlev, geri dönmeden önce başlatılan eş yordalarının bitmesini beklemek için awaitAll() kullanır. Bununla birlikte, awaitAll() adını vermemiş olsak bile coroutineScope oluşturucunun, fetchTwoDocs adlı eş yordamı tüm yeni eş postalar tamamlanana kadar devam ettirmediğini unutmayın.

Buna ek olarak coroutineScope, eş yordamların oluşturduğu istisnaları yakalar ve bunları tekrar arayana yönlendirir.

Paralel ayrıştırma hakkında daha fazla bilgi için Askıya alma işlevleri oluşturma bölümüne bakın.

Eş yordam kavramları

Ortak Kapsam

CoroutineScope, launch veya async kullanarak oluşturduğu tüm eş yordamları takip eder. Devam eden işler (ör. devam eden ortak çalışmalar), dilediğiniz zaman scope.cancel() çağrısı yapılarak iptal edilebilir. Android'de bazı KTX kitaplıkları, belirli yaşam döngüsü sınıfları için kendi CoroutineScope sağlar. Örneğin, ViewModel öğesinde viewModelScope, Lifecycle öğesinde lifecycleScope var. Ancak, bir görev dağıtıcının aksine, CoroutineScope eş yordamları çalıştırmaz.

viewModelScope, Koritinler ile Android'de arka plan ileti dizisi oluşturma bölümündeki örneklerde de kullanılmıştır. Ancak uygulamanızın belirli bir katmanındaki eş yordamların yaşam döngüsünü kontrol etmek için kendi CoroutineScope kodunuzu oluşturmanız gerekirse aşağıdaki gibi oluşturabilirsiniz:

class ExampleClass {

    // Job and Dispatcher are combined into a CoroutineContext which
    // will be discussed shortly
    val scope = CoroutineScope(Job() + Dispatchers.Main)

    fun exampleMethod() {
        // Starts a new coroutine within the scope
        scope.launch {
            // New coroutine that can call suspend functions
            fetchDocs()
        }
    }

    fun cleanUp() {
        // Cancel the scope to cancel ongoing coroutines work
        scope.cancel()
    }
}

İptal edilen bir kapsam daha fazla eş yordam oluşturamaz. Bu nedenle, scope.cancel() yöntemini yalnızca yaşam döngüsünü kontrol eden sınıf kaldırılırken çağırmanız gerekir. viewModelScope kullanıldığında ViewModel sınıfı, ViewModel'in onCleared() yönteminde kapsamı sizin için otomatik olarak iptal eder.

İş

Job, eş yordamı gösteren bir herkese açık kullanıcı adıdır. launch veya async ile oluşturduğunuz her eş yordam, eş yordamı benzersiz şekilde tanımlayan ve yaşam döngüsünü yöneten bir Job örneği döndürür. Aşağıdaki örnekte gösterildiği gibi, yaşam döngüsünü daha ayrıntılı bir şekilde yönetmek için Job öğesini CoroutineScope öğesine de iletebilirsiniz:

class ExampleClass {
    ...
    fun exampleMethod() {
        // Handle to the coroutine, you can control its lifecycle
        val job = scope.launch {
            // New coroutine
        }

        if (...) {
            // Cancel the coroutine started above, this doesn't affect the scope
            // this coroutine was launched in
            job.cancel()
        }
    }
}

Ortak Bağlam

CoroutineContext, aşağıdaki öğe grubunu kullanarak bir eş yordasının davranışını tanımlar:

Bir kapsam dahilinde oluşturulan yeni eş yordamlar için yeni eş yordasına yeni bir Job örneği atanır ve diğer CoroutineContext öğeleri de kapsayıcı kapsamdan devralınır. launch veya async işlevine yeni bir CoroutineContext geçirerek devralınan öğeleri geçersiz kılabilirsiniz. Yeni Job örneği her zaman yeni bir eş kritere atanır. Bu nedenle, Job öğesinin launch veya async öğesine iletilmesinin herhangi bir etkisi olmadığını unutmayın.

class ExampleClass {
    val scope = CoroutineScope(Job() + Dispatchers.Main)

    fun exampleMethod() {
        // Starts a new coroutine on Dispatchers.Main as it's the scope's default
        val job1 = scope.launch {
            // New coroutine with CoroutineName = "coroutine" (default)
        }

        // Starts a new coroutine on Dispatchers.Default
        val job2 = scope.launch(Dispatchers.Default + CoroutineName("BackgroundCoroutine")) {
            // New coroutine with CoroutineName = "BackgroundCoroutine" (overridden)
        }
    }
}

Ek eş yordam kaynakları

Daha fazla eş yordam kaynağı için aşağıdaki bağlantılara bakın: