Android eşzamansız ve engellemeyen API yönergeleri

Engellemeyen API'ler, işin yapılmasını ister ve ardından istenen işlem tamamlanmadan önce diğer işleri yapabilmesi için kontrolü çağıran iş parçacığına geri verir. Bu API'ler, istenen çalışmanın devam ediyor olabileceği veya çalışmanın devam edebilmesi için G/Ç ya da IPC'nin tamamlanmasının, çok çekişmeli sistem kaynaklarının kullanılabilirliğinin ya da kullanıcı girişinin beklenmesi gerekebileceği durumlarda faydalıdır. Özellikle iyi tasarlanmış API'ler, devam eden işlemi iptal etme ve orijinal arayan adına yapılan çalışmayı durdurma olanağı sunar. Böylece, işlem artık gerekli olmadığında sistem sağlığı ve pil ömrü korunur.

Asenkron API'ler, engellemeyen davranış elde etmenin bir yoludur. Asenkron API'ler, işlem tamamlandığında veya işlem devam ederken diğer etkinlikler gerçekleştiğinde bildirilen bir devamlılık ya da geri çağırma biçimini kabul eder.

Asenkron API yazmanın iki temel nedeni vardır:

  • N. işlemin N-1. işlem tamamlanmadan önce başlatılması gereken durumlarda birden fazla işlemin eşzamanlı olarak yürütülmesi.
  • Bir işlem tamamlanana kadar çağırma iş parçacığının engellenmesini önleme.

Kotlin, yapılandırılmış eşzamanlılığı güçlü bir şekilde destekler. Bu, askıya alma işlevleri üzerine kurulu bir dizi ilke ve API'dir. Bu işlevler, kodun eşzamanlı ve eşzamansız yürütülmesini iş parçacığı engelleme davranışından ayırır. Askıya alma işlevleri engellemeyen ve senkronize işlevlerdir.

İşlevleri askıya alma:

  • Bunların çağrı iş parçacığını engellemeyin. Bunun yerine, başka bir yerde yürütülen işlemlerin sonuçları beklenirken yürütme iş parçacıklarını bir uygulama ayrıntısı olarak bırakın.
  • Eşzamanlı olarak yürütülür ve engellemeyen bir API'nin çağrısının, API çağrısı tarafından başlatılan engellemeyen çalışmayla eşzamanlı olarak yürütülmeye devam etmesini gerektirmez.

Bu sayfada, geliştiricilerin engellemeyen ve eşzamansız API'lerle çalışırken güvenle sahip olabileceği minimum temel beklentiler ayrıntılı olarak açıklanmaktadır. Ardından, Android platformunda veya Jetpack kitaplıklarında Kotlin ya da Java dillerinde bu beklentileri karşılayan API'ler oluşturmaya yönelik bir dizi tarif verilmektedir. Emin olmadığınız durumlarda, geliştiricilerin beklentilerini yeni API yüzeylerinin şartları olarak değerlendirin.

Eş zamansız API'lerle ilgili geliştirici beklentileri

Aksi belirtilmediği sürece aşağıdaki beklentiler, askıya alınmayan API'ler açısından yazılmıştır.

Geri çağırmaları kabul eden API'ler genellikle eşzamansızdır.

Bir API, yalnızca yerinde çağrılmak üzere belgelenmemiş bir geri çağırmayı kabul ediyorsa (yani yalnızca API çağrısı döndürülmeden önce çağıran iş parçacığı tarafından çağrılıyorsa) API'nin eşzamansız olduğu varsayılır ve bu API, aşağıdaki bölümlerde belgelenen diğer tüm beklentileri karşılamalıdır.

Yalnızca yerinde çağrılan bir geri çağırma örneği, döndürmeden önce bir koleksiyondaki her öğede bir eşleyici veya yüklem çağıran yüksek sıralı bir harita ya da filtre işlevidir.

Eşzamansız API'ler mümkün olduğunca hızlı yanıt vermelidir.

Geliştiriciler, asenkron API'lerin engellemeyen API'ler olmasını ve işlem isteği başlatıldıktan sonra hızlı bir şekilde yanıt vermesini bekler. Eşzamansız bir API'yi her zaman güvenle çağırabilmeli ve eşzamansız bir API'yi çağırmak asla takılan karelere veya ANR'ye neden olmamalıdır.

Birçok işlem ve yaşam döngüsü sinyali, platform veya kitaplıklar tarafından isteğe bağlı olarak tetiklenebilir. Geliştiricinin, kodu için olası tüm çağrı siteleri hakkında genel bilgiye sahip olmasını beklemek sürdürülebilir değildir. Örneğin, uygulama içeriğinin mevcut alanı (ör. RecyclerView) dolduracak şekilde yerleştirilmesi gerektiğinde View ölçümü ve düzenine yanıt olarak senkronize bir işlemde FragmentManager öğesine Fragment eklenebilir. Bu parçanın onStart yaşam döngüsü geri çağırmasına yanıt veren bir LifecycleObserver, burada tek seferlik başlatma işlemlerini makul bir şekilde gerçekleştirebilir. Bu da, animasyon karesinin sarsıntı olmadan üretilmesi için kritik bir kod yolunda olabilir. Geliştiriciler, bu tür yaşam döngüsü geri çağırmalarına yanıt olarak herhangi bir asenkron API'nin çağrılmasının, karelerin takılmasına neden olmayacağından her zaman emin olmalıdır.

Bu, döndürmeden önce bir asenkron API tarafından gerçekleştirilen çalışmanın çok hafif olması gerektiği anlamına gelir. En fazla, isteğin ve ilişkili geri çağırmanın kaydını oluşturup çalışmayı gerçekleştiren yürütme motoruna kaydetme işlemi yapılabilir. Asenkron bir işlem için kaydolmak IPC gerektiriyorsa API'nin uygulaması, bu geliştirici beklentisini karşılamak için gereken tüm önlemleri almalıdır. Aşağıdakilerden biri veya daha fazlası bu kapsamda olabilir:

  • Temel IPC'yi tek yönlü bağlayıcı çağrısı olarak uygulama
  • Kayıt işleminin tamamlanması için çok çekişmeli bir kilidin alınmasını gerektirmeyen sistem sunucusuna iki yönlü bağlayıcı çağrı yapma
  • IPC üzerinden engelleme kaydı gerçekleştirmek için isteği uygulama sürecindeki bir çalışan iş parçacığına gönderme

Eşzamansız API'ler boş değer döndürmeli ve yalnızca geçersiz bağımsız değişkenler için hata vermelidir.

Asenkron API'ler, istenen işlemin tüm sonuçlarını sağlanan geri çağırmaya bildirmelidir. Bu, geliştiricinin başarı ve hata işleme için tek bir kod yolu uygulamasına olanak tanır.

Asenkron API'ler, bağımsız değişkenlerin boş olup olmadığını kontrol edebilir ve NullPointerException hata mesajını verebilir veya sağlanan bağımsız değişkenlerin geçerli bir aralıkta olup olmadığını kontrol edip IllegalArgumentException hata mesajını verebilir. Örneğin, float ile 1f aralığında bir 0 kabul eden bir işlev için işlev, parametrenin bu aralıkta olup olmadığını kontrol edebilir ve aralığın dışında ise IllegalArgumentException değerini döndürebilir. Alternatif olarak, kısa bir String, yalnızca alfanümerik karakterler gibi geçerli bir biçime uygunluk açısından kontrol edilebilir. (Sistem sunucusunun uygulama sürecine hiçbir zaman güvenmemesi gerektiğini unutmayın. Tüm sistem hizmetleri, bu kontrolleri sistem hizmetinin kendisinde de tekrarlamalıdır.)

Diğer tüm hatalar, sağlanan geri arama işlevine bildirilmelidir. Bu, aşağıdakileri kapsar ancak bunlarla sınırlı değildir:

  • İstenen işlemde terminal hata
  • İşlemi tamamlamak için gereken yetki veya izinlerin eksik olmasıyla ilgili güvenlik istisnaları
  • İşlemi gerçekleştirme kotası aşıldı
  • Uygulama işlemi, işlemi gerçekleştirmek için yeterince "ön planda" değil
  • Gerekli donanımın bağlantısı kesildi
  • Ağ hataları
  • Engelleme
  • Bağlayıcı ölüm veya kullanılamayan uzak işlem

Eşzamansız API'ler bir iptal mekanizması sağlamalıdır

Asenkron API'ler, çalışan bir işleme arayanın artık sonuçla ilgilenmediğini belirtmenin bir yolunu sağlamalıdır. Bu iptal işlemi iki şeyi belirtmelidir:

Arayan tarafından sağlanan geri aramalarla ilgili kesin referanslar yayınlanmalıdır.

Asenkron API'lere sağlanan geri çağırmalar, büyük nesne grafiklerine sabit referanslar içerebilir ve bu geri çağırmaya sabit referans içeren devam eden çalışmalar, bu nesne grafiklerinin çöp toplama işlemine alınmasını engelleyebilir. Bu geri çağırma referansları iptal üzerine serbest bırakıldığında, bu nesne grafikleri, işin tamamlanmasına izin verilmesi durumuna kıyasla çok daha kısa sürede çöp toplama işlemine uygun hale gelebilir.

Arayan için iş yapan yürütme motoru, bu işi durdurabilir.

Asenkron API çağrılarıyla başlatılan işlemler, güç tüketimi veya diğer sistem kaynakları açısından yüksek maliyetli olabilir. Arayanların bu işin artık gerekli olmadığını bildirmesine olanak tanıyan API'ler, bu işin daha fazla sistem kaynağı tüketmeden durdurulmasına izin verir.

Önbelleğe alınan veya dondurulmuş uygulamalarla ilgili dikkat edilmesi gereken noktalar

Geri çağırmaların bir sistem sürecinde başladığı ve uygulamalara teslim edildiği asenkron API'ler tasarlarken aşağıdakileri göz önünde bulundurun:

  1. Süreçler ve uygulama yaşam döngüsü: Alıcı uygulama süreci önbelleğe alınmış durumda olabilir.
  2. Önbelleğe alınmış uygulamaları dondurma: Alıcı uygulama işlemi dondurulmuş olabilir.

Bir uygulama süreci önbelleğe alınmış duruma girdiğinde, etkinlikler ve hizmetler gibi kullanıcı tarafından görülebilen bileşenleri etkin olarak barındırmadığı anlamına gelir. Uygulama, tekrar kullanıcı tarafından görünür hale gelmesi ihtimaline karşı bellekte tutulur ancak bu süre zarfında herhangi bir işlem yapmamalıdır. Çoğu durumda, uygulamanın önbelleğe alınmış duruma girmesiyle birlikte uygulama geri çağırmalarının gönderilmesini duraklatmanız ve uygulamanın önbelleğe alınmış durumdan çıkmasıyla birlikte devam ettirmeniz gerekir. Böylece, önbelleğe alınmış uygulama işlemlerinde işleme neden olmazsınız.

Önbelleğe alınmış bir uygulama da dondurulabilir. Dondurulan uygulamalar CPU süresi almaz ve hiçbir işlem yapamaz. Bu uygulamanın kayıtlı geri aramalarına yapılan tüm aramalar arabelleğe alınır ve uygulama dondurulması kaldırıldığında teslim edilir.

Uygulama geri çağırmalarına yönelik arabelleğe alınmış işlemler, uygulama dondurulması kaldırılıp bu işlemleri gerçekleştirdiğinde eski olabilir. Arabellek sınırlıdır ve taşarsa alıcı uygulamanın kilitlenmesine neden olur. Uygulamaların eski etkinliklerle aşırı yüklenmesini veya arabelleklerinin taşmasını önlemek için işlemleri dondurulmuşken uygulama geri çağırmalarını göndermeyin.

İnceleniyor:

  • Uygulamanın işlemi önbelleğe alınırken uygulama geri aramalarının gönderilmesini duraklatmayı düşünebilirsiniz.
  • Uygulamanın işlemi dondurulmuşken uygulama geri çağırmalarının gönderimini DURAKLATMANIZ GEREKİR.

Durum izleme

Uygulamaların ne zaman önbelleğe alınmış duruma girdiğini veya bu durumdan çıktığını izlemek için:

mActivityManager.addOnUidImportanceListener(
    new UidImportanceListener() { ... },
    IMPORTANCE_CACHED);

Uygulamaların ne zaman dondurulduğunu veya dondurmalarının kaldırıldığını izlemek için:

IBinder binder = <...>;
binder.addFrozenStateChangeCallback(executor, callback);

Uygulama geri çağırmalarını göndermeye devam etme stratejileri

Uygulama önbelleğe alınmış duruma veya dondurulmuş duruma girdiğinde uygulama geri çağırmalarının gönderimini duraklatıp duraklatmadığınızdan bağımsız olarak, uygulama ilgili durumdan çıktığında, geri çağırmasını kaydını silene veya uygulama işlemi sonlanana kadar uygulamanın kayıtlı geri çağırmalarını göndermeye devam etmeniz gerekir.

Örneğin:

IBinder binder = <...>;
bool shouldSendCallbacks = true;
binder.addFrozenStateChangeCallback(executor, (who, state) -> {
    if (state == IBinder.FrozenStateChangeCallback.STATE_FROZEN) {
        shouldSendCallbacks = false;
    } else if (state == IBinder.FrozenStateChangeCallback.STATE_UNFROZEN) {
        shouldSendCallbacks = true;
    }
});

Alternatif olarak, hedef işlem dondurulduğunda geri çağırmaların teslim edilmemesini sağlayan RemoteCallbackList kullanabilirsiniz.

Örneğin:

RemoteCallbackList<IInterface> rc =
        new RemoteCallbackList.Builder<IInterface>(
                        RemoteCallbackList.FROZEN_CALLEE_POLICY_DROP)
                .setExecutor(executor)
                .build();
rc.register(callback);
rc.broadcast((callback) -> callback.foo(bar));

callback.foo() yalnızca işlem dondurulmamışsa çağrılır.

Uygulamalar, geri çağırmaları kullanarak aldıkları güncellemeleri genellikle son durumun anlık görüntüsü olarak kaydeder. Uygulamaların kalan pil yüzdesini izlemesini sağlayan varsayıma dayalı bir API'yi ele alalım:

interface BatteryListener {
    void onBatteryPercentageChanged(int newPercentage);
}

Bir uygulama dondurulduğunda birden fazla durum değişikliği etkinliğinin gerçekleştiği senaryoyu ele alalım. Uygulamanın dondurulması kaldırıldığında uygulamaya yalnızca en son durumu iletmeniz ve diğer eski durum değişikliklerini bırakmanız gerekir. Bu teslimat, uygulama dondurulması kaldırıldığında hemen gerçekleşmelidir. Böylece uygulama "güncel" hale gelebilir. Bu işlem şu şekilde yapılabilir:

RemoteCallbackList<IInterface> rc =
        new RemoteCallbackList.Builder<IInterface>(
                        RemoteCallbackList.FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT)
                .setExecutor(executor)
                .build();
rc.register(callback);
rc.broadcast((callback) -> callback.onBatteryPercentageChanged(value));

Bazı durumlarda, uygulamaya teslim edilen son değeri izleyebilirsiniz. Böylece uygulama, dondurulması kaldırıldıktan sonra aynı değer için bildirim almaz.

Durum daha karmaşık verilerle ifade edilebilir. Uygulamaların ağ arayüzleri hakkında bildirim alması için varsayımsal bir API düşünün:

interface NetworkListener {
    void onAvailable(Network network);
    void onLost(Network network);
    void onChanged(Network network);
}

Bir uygulamaya yönelik bildirimleri duraklatırken uygulamanın en son gördüğü ağları ve durumları hatırlamanız gerekir. Devam ettirildikten sonra, sırasıyla kaybolan eski ağları, kullanılabilir hale gelen yeni ağları ve durumu değişen mevcut ağları uygulamaya bildirmeniz önerilir.

Geri çağırmalar duraklatılırken kullanılabilir hale gelen ve ardından kaybedilen ağlar hakkında uygulamaya bildirimde bulunmayın. Uygulamalar, donduruldukları sırada gerçekleşen olayların tam dökümünü almamalıdır ve API belgelerinde, açık yaşam döngüsü durumları dışında kesintisiz etkinlik akışları sunulacağı vaat edilmemelidir. Bu örnekte, uygulamanın ağ kullanılabilirliğini sürekli olarak izlemesi gerekiyorsa uygulamanın önbelleğe alınmasını veya dondurulmasını engelleyen bir yaşam döngüsü durumunda kalması gerekir.

İnceleme sırasında, bildirimler duraklatıldıktan sonra ve yeniden başlatılmadan önce gerçekleşen etkinlikleri birleştirip kayıtlı uygulama geri çağırmalarına en son durumu kısa ve öz bir şekilde iletmeniz gerekir.

Geliştirici belgeleriyle ilgili dikkat edilmesi gereken noktalar

Asenkron etkinliklerin teslimatı, gönderenin önceki bölümde gösterildiği gibi teslimatı bir süre duraklatması veya alıcı uygulamanın etkinliği zamanında işlemek için yeterli cihaz kaynağı almaması nedeniyle gecikebilir.

Geliştiricilerin, uygulamalarına bir etkinlik bildirimi gönderilmesi ile etkinliğin gerçekleşmesi arasındaki süre hakkında varsayımlarda bulunmasını önleyin.

API'lerin askıya alınmasıyla ilgili geliştirici beklentileri

Kotlin'in yapılandırılmış eşzamanlılığına aşina olan geliştiriciler, askıya alma işlevine sahip API'lerden aşağıdaki davranışları bekler:

Askıya alma işlevleri, geri dönmeden veya atılmadan önce ilişkili tüm işleri tamamlamalıdır.

Engellemeyen işlemlerin sonuçları normal işlev dönüş değerleri olarak döndürülür ve hatalar istisnalar oluşturularak bildirilir. (Bu genellikle geri çağırma parametrelerinin gereksiz olduğu anlamına gelir.)

Askıya alma işlevleri, geri çağırma parametrelerini yalnızca yerinde çağırmalıdır

Askıya alma işlevleri, geri dönmeden önce her zaman ilişkili tüm çalışmaları tamamlamalıdır. Bu nedenle, sağlanan bir geri çağırma işlevini veya başka bir işlev parametresini asla çağırmamalı ya da askıya alma işlevi döndükten sonra buna referans tutmamalıdır.

Geri çağırma parametrelerini kabul eden askıya alma işlevleri, aksi belirtilmediği sürece bağlamı korumalıdır.

Bir askıya alma işlevinde işlev çağırmak, işlevin arayanın CoroutineContext içinde çalışmasına neden olur. Askıya alma işlevleri, döndürme veya hata oluşturma işleminden önce ilişkili tüm işleri tamamlamalı ve geri çağırma parametrelerini yalnızca yerinde çağırmalıdır. Bu nedenle, bu tür geri çağırmaların, ilişkili göndericisi kullanılarak çağıran CoroutineContext üzerinde de çalıştırılması beklenir. API'nin amacı, çağırma kapsamı dışında bir geri çağırma işlevi çalıştırmaksa bu davranış açıkça belgelenmelidir.CoroutineContext

Askıya alma işlevleri, kotlinx.coroutines iş iptalini desteklemelidir.

Sunulan tüm askıya alma işlevleri, kotlinx.coroutines tarafından tanımlandığı şekilde iş iptaliyle uyumlu olmalıdır. Devam eden bir işlemin çağırma işi iptal edilirse, çağıranın temizlik yapıp en kısa sürede devam edebilmesi için işlev en kısa sürede CancellationException ile devam etmelidir. Bu işlem, suspendCancellableCoroutine ve kotlinx.coroutines tarafından sunulan diğer askıya alma API'leri tarafından otomatik olarak gerçekleştirilir. Kitaplık uygulamaları, bu iptal davranışını varsayılan olarak desteklemediği için genellikle doğrudan suspendCoroutine kullanmamalıdır.

Arka planda (ana olmayan veya kullanıcı arayüzü iş parçacığı) engelleme işi yapan işlevler, kullanılan göndericiyi yapılandırma yöntemi sağlamalıdır.

İş parçacıklarını değiştirmek için engelleme işlevinin tamamen askıya alınması önerilmez.

Bir askıya alma işlevinin çağrılması, geliştiricinin bu işi gerçekleştirmek için kendi iş parçacığını veya iş parçacığı havuzunu sağlamasına izin verilmeden ek iş parçacıklarının oluşturulmasına neden olmamalıdır. Örneğin, bir oluşturucu, sınıfın yöntemleri için arka plan çalışması yapmak üzere kullanılan bir CoroutineContext kabul edebilir.

Engelleme işlemini gerçekleştirmek için yalnızca bu göndericiye geçmek üzere isteğe bağlı bir CoroutineContext veya Dispatcher parametresini kabul eden işlevler askıya alınmalıdır. Bunun yerine, temel engelleme işlevi kullanıma sunulmalı ve çağıran geliştiricilerin, işi seçilen bir göndericiye yönlendirmek için kendi withContext çağrılarını kullanmaları önerilmelidir.

Coroutine başlatan sınıflar

Coroutine'leri başlatan sınıfların, bu başlatma işlemlerini gerçekleştirmek için CoroutineScope olması gerekir. Yapılandırılmış eşzamanlılık ilkelerine uymak, bu kapsamı elde etmek ve yönetmek için aşağıdaki yapısal kalıpları kullanmayı gerektirir.

Eşzamanlı görevleri başka bir kapsama başlatan bir sınıf yazmadan önce alternatif kalıpları göz önünde bulundurun:

class MyClass {
    private val requests = Channel<MyRequest>(Channel.UNLIMITED)

    suspend fun handleRequests() {
        coroutineScope {
            for (request in requests) {
                // Allow requests to be processed concurrently;
                // alternatively, omit the [launch] and outer [coroutineScope]
                // to process requests serially
                launch {
                    processRequest(request)
                }
            }
        }
    }

    fun submitRequest(request: MyRequest) {
        requests.trySend(request).getOrThrow()
    }
}

Eşzamanlı çalışma yapmak için bir suspend fun kullanıma sunulduğunda arayan, işlemi kendi bağlamında çağırabilir. Bu sayede MyClass'nin CoroutineScope yönetmesine gerek kalmaz. İsteklerin işlenmesini serileştirmek daha kolay hale gelir ve durum, genellikle ek senkronizasyon gerektirecek sınıf özellikleri yerine handleRequests'nın yerel değişkenleri olarak var olabilir.

Coroutine'ları yöneten sınıflar, kapatma ve iptal etme yöntemlerini kullanıma sunmalıdır

Uygulama ayrıntıları olarak eşzamanlı yordamları başlatan sınıflar, devam eden eşzamanlı görevleri temiz bir şekilde kapatmanın bir yolunu sunmalıdır. Böylece, kontrolsüz eşzamanlı işler üst kapsama sızmaz. Bu işlem genellikle sağlanan bir CoroutineContext öğesinin Job alt öğesini oluşturma şeklinde yapılır:

private val myJob = Job(parent = `CoroutineContext`[Job])
private val myScope = CoroutineScope(`CoroutineContext` + myJob)

fun cancel() {
    myJob.cancel()
}

Ayrıca, kullanıcı kodunun nesne tarafından gerçekleştirilen eşzamanlı işlemlerin tamamlanmasını beklemesine izin vermek için bir join() yöntemi de sağlanabilir. (Bu, bir işlemi iptal ederek gerçekleştirilen temizleme çalışmalarını içerebilir.)

suspend fun join() {
    myJob.join()
}

Terminal işlemi adlandırma

Bir nesnenin sahip olduğu ve hâlâ devam eden eşzamanlı görevleri düzgün bir şekilde kapatmak için kullanılan yöntemlerin adı, kapatma işleminin nasıl gerçekleştiğiyle ilgili davranış sözleşmesini yansıtmalıdır:

Devam eden işlemlerin tamamlanabileceği ancak close() çağrısı döndükten sonra yeni işlemlerin başlatılamayacağı durumlarda close() kullanın.

Devam eden işlemler tamamlanmadan önce iptal edilebileceğinde cancel() simgesini kullanın. cancel() çağrısı döndükten sonra yeni işlemler başlatılamaz.

Sınıf oluşturucuları, CoroutineScope değil CoroutineContext kabul eder

Nesnelerin doğrudan sağlanan bir üst kapsamda başlatılması yasaklandığında, CoroutineScope öğesinin oluşturucu parametresi olarak uygunluğu bozulur:

// Don't do this
class MyClass(scope: CoroutineScope) {
    private val myJob = Job(parent = scope.`CoroutineContext`[Job])
    private val myScope = CoroutineScope(scope.`CoroutineContext` + myJob)

    // ... the [scope] constructor parameter is never used again
}

CoroutineScope, bazı kullanım alanlarında yalnızca oluşturucu parametresi olarak geçmek için oluşturulabilen, gereksiz ve yanıltıcı bir sarmalayıcı haline gelir ve daha sonra atılır:

// Don't do this; just pass the context
val myObject = MyClass(CoroutineScope(parentScope.`CoroutineContext` + Dispatchers.IO))

CoroutineContext parametreleri varsayılan olarak EmptyCoroutineContext'e ayarlanır.

Bir API yüzeyinde isteğe bağlı bir CoroutineContext parametresi göründüğünde varsayılan değer Empty`CoroutineContext` sentinel olmalıdır. Bu, arayan tarafından sağlanan Empty`CoroutineContext` değerinin varsayılanı kabul etmekle aynı şekilde ele alınması nedeniyle API davranışlarının daha iyi oluşturulmasına olanak tanır:

class MyOuterClass(
    `CoroutineContext`: `CoroutineContext` = Empty`CoroutineContext`
) {
    private val innerObject = MyInnerClass(`CoroutineContext`)

    // ...
}

class MyInnerClass(
    `CoroutineContext`: `CoroutineContext` = Empty`CoroutineContext`
) {
    private val job = Job(parent = `CoroutineContext`[Job])
    private val scope = CoroutineScope(`CoroutineContext` + job)

    // ...
}