Kotlin eş yordamları, ağ çağrıları veya disk işlemleri gibi uzun süren görevleri yönetirken uygulamanızın yanıt vermesini sağlayan temiz ve basitleştirilmiş eşzamansız kod yazmanıza olanak tanır.
Bu konuda, Android'deki eş yordamlar ayrıntılı olarak ele alınmaktadır. Eş yordamlar hakkında bilginiz yoksa bu konuyu okumadan önce Android'de Kotlin eş yordamları başlıklı makaleyi okuduğunuzdan emin olun.
Uzun süreli görevleri yönetme
Coroutines, uzun süren görevleri yönetmek için iki işlem ekleyerek normal işlevler üzerine kurulur. invoke (veya call) ve return'ye ek olarak, eş yordamlar suspend ve resume'i ekler:
suspend, geçerli eşzamanlı rutinin yürütülmesini duraklatır ve tüm yerel değişkenleri kaydeder.resume, askıya alınmış bir eş yordamın yürütülmesine askıya alındığı yerden devam eder.
suspend işlevlerini yalnızca diğer suspend işlevlerinden veya yeni bir eşzamanlılık başlatmak için launch gibi bir eşzamanlılık oluşturucu kullanarak çağırabilirsiniz.
Aşağıdaki örnekte, varsayıma dayalı uzun süren 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 coroutine'i askıya alır. Ağ isteği tamamlandığında get, ana iş parçacığını bilgilendirmek için geri çağırma işlevi kullanmak yerine askıya alınmış eş yordamı devam ettirir.
Kotlin, hangi işlevin yerel değişkenlerle birlikte çalıştığını yönetmek için yığın çerçevesi kullanır. Bir ortak yordam askıya alındığında mevcut yığın çerçevesi kopyalanır ve daha sonra kullanılmak üzere kaydedilir. Devam ettirildiğinde yığın çerçevesi kaydedildiği yerden geri 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ı engellemesini önler.
Ana iş parçacığında güvenli olmak için coroutine'leri kullanın
Kotlin eş yordamları, eş yordam yürütme için hangi iş parçacıklarının kullanılacağını belirlemek üzere dispatchers'ı kullanır. Kodu ana iş parçacığının dışında çalıştırmak için Kotlin coroutines'a Default veya IO dağıtıcıda çalışmasını söyleyebilirsiniz. Kotlin'de tüm coroutine'ler ana iş parçacığında çalışırken bile bir dağıtıcıda çalışmalıdır. Coroutine'ler kendilerini askıya alabilir ve bunları devam ettirmekten gönderici sorumludur.
Kotlin, eş yordamların nerede çalışacağını belirtmek için kullanabileceğiniz üç dağıtıcı sağlar:
- Dispatchers.Main: Bir coroutine'i ana Android iş parçacığında çalıştırmak için bu dağıtıcıyı kullanın. Bu işlev yalnızca kullanıcı arayüzüyle etkileşim kurmak ve hızlı işlemler yapmak için kullanılmalıdır.
suspendişlevlerini çağırma, Android kullanıcı arayüzü çerçevesi işlemlerini çalıştırma veLiveDatanesnelerini güncelleme buna örnek olarak verilebilir. - Dispatchers.IO: Bu dağıtıcı, ana iş parçacığı dışında disk veya ağ G/Ç'si gerçekleştirecek şekilde optimize edilmiştir. Örnek olarak Room bileşeninin kullanılması, dosyalardan okuma veya dosyalara yazma işlemleri ve herhangi bir ağ işleminin çalıştırılması verilebilir.
- Dispatchers.Default: Bu dağıtıcı, CPU yoğun işleri ana iş parçacığının dışında gerçekleştirecek şekilde optimize edilmiştir. Liste sıralama ve JSON ayrıştırma, örnek kullanım alanları arasındadır.
Önceki örneğe devam edersek göndericileri kullanarak get işlevini yeniden tanımlayabilirsiniz. get gövdesinde, withContext(Dispatchers.IO) işlevini çağırarak IO iş parçacığı havuzunda çalışan bir blok oluşturun. Bu bloğun içine yerleştirdiğiniz tüm kodlar her zaman IO dağıtıcı aracılığıyla yürütülür. withContext işlevi 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ş yordamlarla, iş parçacıklarını ayrıntılı kontrolle gönderebilirsiniz. withContext(), geri çağırmalar eklemeden herhangi bir kod satırının iş parçacığı havuzunu kontrol etmenize olanak tanıdığı için bunu veritabanından okuma veya ağ isteği gerçekleştirme gibi çok küçük işlevlere uygulayabilirsiniz. Her işlevin ana işleme güvenli olduğundan emin olmak için withContext() kullanmak iyi bir uygulamadır. Bu, işlevi ana iş parçacığından çağırabileceğiniz anlamına gelir. Bu şekilde, arayanın işlevi yürütmek için hangi iş parçacığının kullanılması gerektiğini düşünmesi gerekmez.
Önceki örnekte fetchDocs(), ana iş parçacığında yürütülür ancak arka planda ağ isteği gerçekleştiren get'u güvenli bir şekilde çağırabilir.
Coroutine'ler suspend ve resume'ı desteklediğinden, withContext bloğu tamamlanır tamamlanmaz ana iş parçacığındaki coroutine, get sonucuyla devam ettirilir.
withContext() işlevinin performansı
withContext(), eşdeğer bir geri çağırma tabanlı uygulamaya kıyasla ek yük getirmez. Ayrıca, bazı durumlarda withContext() aramalarını eşdeğer bir geri arama tabanlı uygulamadan daha fazla optimize etmek mümkündür. Örneğin, bir işlev ağa on kez çağrı yapıyorsa dış withContext() kullanarak Kotlin'e yalnızca bir kez iş parçacığı değiştirmesini söyleyebilirsiniz. Bu durumda, ağ kitaplığı withContext() işlevini birden çok kez kullanmasına rağmen aynı dağıtıcıda kalır ve iş parçacıklarını değiştirmekten kaçınır. Ayrıca Kotlin, mümkün olduğunda iş parçacığı geçişlerini önlemek için Dispatchers.Default ve Dispatchers.IO arasında geçişi optimize eder.
Coroutine başlatma
Eş yordamları iki şekilde başlatabilirsiniz:
launchyeni bir eş yordam başlatır ve sonucu arayana döndürmez. "Fire and forget" (ateşle ve unut) olarak kabul edilen tüm çalışmalarlaunchkullanılarak başlatılabilir.asyncyeni bir eş yordam başlatır veawaitadlı bir askıya alma işleviyle sonuç döndürmenize olanak tanır.
Normal bir işlev await işlevini çağıramadığından genellikle normal bir işlevden launch yeni bir eşzamanlı rutin oluşturmanız gerekir. async işlevini yalnızca başka bir eşzamanlı yordamın veya askıya alma işlevinin içinde ve paralel ayrıştırma işlemi gerçekleştirirken kullanın.
Paralel ayrıştırma
suspend işlevi içinde başlatılan tüm eşzamanlı rutinler, bu işlev döndüğünde durdurulmalıdır. Bu nedenle, döndürmeden önce bu eşzamanlı rutinlerin tamamlandığından emin olmanız gerekir. Kotlin'deki yapılandırılmış eşzamanlılık ile bir veya daha fazla eşzamanlı rutin başlatan bir coroutineScope tanımlayabilirsiniz. Ardından, await() (tek bir eşzamanlı rutin için) veya awaitAll() (birden fazla eşzamanlı rutin için) kullanarak bu eşzamanlı rutinlerin işlevden dönmeden önce tamamlanmasını sağlayabilirsiniz.
Örneğin, iki belgeyi eşzamansız olarak getiren bir coroutineScope tanımlayalım. Ertelenen her referansta await() çağrısı yaparak, bir değer döndürülmeden önce her iki async işlemin de tamamlanacağını garanti ederiz:
suspend fun fetchTwoDocs() = coroutineScope { val deferredOne = async { fetchDoc(1) } val deferredTwo = async { fetchDoc(2) } deferredOne.await() deferredTwo.await() }
Ayrıca, aşağıdaki örnekte gösterildiği gibi koleksiyonlarda da awaitAll() 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şzamanlı rutinler başlatsa da işlev, döndürmeden önce başlatılan eşzamanlı rutinlerin tamamlanmasını beklemek için awaitAll() kullanır. Ancak awaitAll() işlevini çağırmamış olsak bile coroutineScope oluşturucunun, yeni eş yordamların tamamı bitene kadar fetchTwoDocs işlevini çağıran eş yordamı sürdürmediğini unutmayın.
Ayrıca, coroutineScope, eş yordamların oluşturduğu tüm istisnaları yakalar ve arayana geri yönlendirir.
Paralel ayrıştırma hakkında daha fazla bilgi için Askıya alma işlevlerini oluşturma başlıklı makaleyi inceleyin.
Coroutines kavramları
CoroutineScope
Bir CoroutineScope, launch veya async kullanarak oluşturduğu tüm eşzamanlı yordamları takip eder. Devam eden çalışma (ör. çalışan eş yordamlar), herhangi bir zamanda scope.cancel() çağrılarak iptal edilebilir. Android'de bazı KTX kitaplıkları, belirli yaşam döngüsü sınıfları için kendi CoroutineScope'lerini sağlar. Örneğin, ViewModel öğesinin viewModelScope, Lifecycle öğesinin ise lifecycleScope öğesi var.
Ancak bir göndericiden farklı olarak CoroutineScope, eş yordamları çalıştırmaz.
viewModelScope, Android'de eş yordamlarla arka plan iş parçacığı oluşturma bölümündeki örneklerde de kullanılır.
Ancak uygulamanızın belirli bir katmanındaki eş yordamların yaşam döngüsünü kontrol etmek için kendi CoroutineScope öğenizi oluşturmanız gerekiyorsa bunu aşağıdaki şekilde yapabilirsiniz:
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şzamanlı yordam oluşturamaz. Bu nedenle, scope.cancel() yalnızca yaşam döngüsünü kontrol eden sınıf yok edildiğinde çağrılmalıdır. viewModelScope kullanılırken ViewModel sınıfı, ViewModel'in onCleared() yönteminde kapsamı sizin için otomatik olarak iptal eder.
İş
Job, eşzamanlı yordamın 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. Ayrıca, aşağıdaki örnekte gösterildiği gibi yaşam döngüsünü daha iyi 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 (condition) { // Cancel the coroutine started above, this doesn't affect the scope // this coroutine was launched in job.cancel() } } }
CoroutineContext
CoroutineContext
Aşağıdaki öğe grubunu kullanarak bir eş yordamın davranışını tanımlar:
Job: Eş yordamın yaşam döngüsünü kontrol eder.CoroutineDispatcher: İşi uygun iş parçacığına gönderir.CoroutineName: Eş yordamın adı. Hata ayıklama için kullanışlıdır.CoroutineExceptionHandler: Yakalanmamış istisnaları işler.
Bir kapsam içinde oluşturulan yeni eş yordamlar için yeni eş yordama yeni bir Job örneği atanır ve diğer CoroutineContext öğeleri kapsayan kapsamdan devralınır. Yeni bir CoroutineContext öğesini launch veya async işlevine ileterek devralınan öğeleri geçersiz kılabilirsiniz. Job öğesini launch veya async öğesine geçirmenin hiçbir etkisi olmadığını unutmayın.
Çünkü Job öğesinin yeni bir örneği her zaman yeni bir eşzamanlı işleme atanır.
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 coroutine kaynağı için aşağıdaki bağlantılara bakın: