withContext — bir coroutine içinde yürütme bağlamını değiştiren, belirtilen kod bloğu için geçici olarak iş parçacığını veya dağıtıcıyı değiştiren ve sonucu orijinal bağlama döndüren bir fonksiyondur. JetBrains, 2025'e göre, withContext ağ istekleri ve disk işlemleri için en sık kullanılan coroutine araçlarından biridir. Fonksiyon, blok tamamlandıktan sonra coroutine'in orijinal dağıtıcıda yürütmeye devam etmesini garanti ederek kazara iş parçacığı güvenliği hatalarını önler.
Önemli Noktalar
withContext, kotlinx.coroutines paketinden bir askıya alma fonksiyonudur; iletilen kod bloğunu belirtilen bir CoroutineContext'te yürütür ve sonucu orijinal bağlama döndürür. Fonksiyon imzası şöyledir:
suspend fun withContext (
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T
context parametresi herhangi bir CoroutineContext'i kabul eder — en yaygın olarak standart Dispatchers.IO, Dispatchers.Default veya Dispatchers.Main'den biri. Blok bu bağlamda yürütülür ve sonuç, withContext'in çağrıldığı yere döndürülür.
Lambda tamamlandıktan sonra, withContext yürütmeyi garantili bir şekilde orijinal dağıtıcıya geri döndürür. Bu, geliştiricinin arka plan işleminden sonra manuel olarak withContext(Dispatchers.Main) çağırması gerekmediği anlamına gelir — geri dönüş otomatik olarak gerçekleşir. Bu davranış, Kotlin Coroutines spesifikasyonunda sürüm 1.3'ten beri belgelenmiştir.
Android geliştirme, withContext'in ana kullanım alanıdır. Tipik bir senaryo: Bir ViewModel, ana iş parçacığında bir coroutine başlatır, içinde bir ağ isteği için withContext(Dispatchers.IO) çağırır ve Main'e otomatik dönüşten sonra sonuç UI'yi güncellemek için kullanılır. Bu yaklaşım, MVVM mimarisinin temelidir ve Google tarafından resmi coroutine kılavuzunda önerilir.
withContext'i anlamak için CoroutineContext ve onun temel bileşeni olan dağıtıcıyı anlamanız gerekir. Her coroutine'in bir dizi bağlam öğesi vardır ve bunlar arasında dağıtıcı, kodun hangi iş parçacığında veya iş parçacığı havuzunda çalıştırılacağını belirler.
| Dağıtıcı | Amaç | Havuz boyutu |
|---|---|---|
| Dispatchers.Main | Ana UI iş parçacığı (Android, JavaFX, Swing) | 1 (ana iş parçacığı) |
| Dispatchers.IO | Disk ve ağ işlemleri | 64 iş parçacığı (sınır büyür) |
| Dispatchers.Default | CPU yoğun hesaplamalar | max(2, çekirdek sayısı) |
| Dispatchers.Unconfined | Sabit iş parçacığı yok | sınırsız |
withContext'in yeni bir coroutine oluşturmadığını anlamak önemlidir — sadece mevcut olanın bağlamını değiştirir. Bu, yeni coroutine'ler oluşturan launch ve async'den temel farktır. withContext'in iç uygulaması optimize edilmiştir: istenen bağlam mevcut bağlamla eşleşirse, değişiklik olmaz — fonksiyon aynı dağıtıcıda yürütülür.
withContext(Dispatchers.Main) içindeki Dispatchers.Main bir değişikliğe neden olmaz — Kotlin Coroutines bağlamların kimliğini tanır ve gereksiz işlemi atlar. Benzer şekilde, zaten Default üzerinde çalışan bir coroutine içindeki withContext(Dispatchers.Default) ek yük oluşturmaz. Bu optimizasyon ContinuationInterceptor içinde uygulanmıştır.
Yeni başlayanlar genellikle withContext'i launch ve async ile karıştırır, çünkü her üç fonksiyon da coroutine'ler ve bağlam ile çalışır. Ancak amaçları temelde farklıdır.
| Özellik | withContext | launch | async |
|---|---|---|---|
| Yeni coroutine oluşturur | Hayır | Evet | Evet |
| Sonuç döndürür | Evet (T doğrudan) | Hayır (Job) | Evet (Deferred<T>) |
| Yürütme | Sıralı | Paralel | Paralel |
| Sonuç bekleme | Otomatik | join() | await() |
| Tipik kullanım durumu | Dağıtıcı değiştirme | Ateşle-unut | Paralel hesaplamalar |
Arka plan iş parçacığında bir işlem yürütmeniz ve sonuç almanız gerekiyorsa — withContext kullanın. Birden fazla bağımsız işlemi paralel olarak çalıştırmanız gerekiyorsa — await ile async kullanın. Sonuca ihtiyacınız yoksa (günlükleme, önbellek yazma) — launch kullanın. Google, Android mimarisinde Repository katmanı için withContext'i tercih edilen araç olarak önerir.
Kotlin Android uygulamalarında withContext kullanımının üç pratik senaryosuna bakalım. Her örnek belirli bir görevi ve doğru deseni gösterir.
Bir ViewModel, Main üzerindeki bir coroutine'den depo yöntemini çağırır. İçeride, withContext(Dispatchers.IO) bir HTTP isteği yürütür ve sonuç otomatik olarak döndürülür:
class UserRepository(
private val api: UserApi
) {
suspend fun getUser(id: String): User {
return withContext(Dispatchers.IO) {
api.fetchUser(id)
}
}
}
ViewModel'deki coroutine, getUser'ı herhangi bir normal askıya alma fonksiyonu gibi çağırır — dağıtıcıyı açıkça belirtmeden. withContext, iş parçacığı değiştirmenin ayrıntılarını gizler.
Birden fazla IO işlemini art arda yürütmeniz gerektiğinde, withContext bunları tek bir blokta birleştirir. Bu, her işlemi ayrı bir withContext'e sarmaktan daha verimlidir:
suspend fun loadUserProfile(id: String): Profile {
return withContext(Dispatchers.IO) {
val user = api.fetchUser(id)
val posts = api.fetchPosts(id)
Profile(user, posts)
}
}
Her iki işlem de Dispatchers.IO üzerinde çalışır ve Profile sonucu gereksiz bağlam değişiklikleri olmadan oluşturulur ve döndürülür. İşlemler bağımsızsa, paralel yürütme için async kullanmak daha iyidir.
Bazı senaryolarda, iptal edilemeyecek kodu yürütmeniz gerekir — örneğin, bir ekranı kapatırken durumu kaydetme. withContext + NonCancellable kombinasyonu bu görevi çözer:
withContext(Dispatchers.IO + NonCancellable) {
cache.saveState(state)
analytics.logEvent("state_saved")
}
+ operatörü iki bağlam öğesini birleştirir: IO dağıtıcısı ve NonCancellable bayrağı. Blok, üst coroutine iptal edilmiş olsa bile yürütülür — sonlandırma işlemleri için kullanışlıdır.
withContext'in iç uygulaması Continuation mekanizmasına dayanır — Kotlin coroutine'lerinin merkezi soyutlamasıdır. Her askıya alma noktası, yürütme durumunu bir Continuation nesnesinde kaydeder ve withContext bir istisna değildir.
Kotlin derleyicisi, withContext'i kotlinx.coroutines'deki withContext yöntemine bir çağrı olarak çevirir ve bu yöntem dahili olarak yeni bir DispatchedContinuation örneği oluşturur. Bu nesne, orijinal Continuation'ı sarar ve dağıtıcısını değiştirir. Yeni dağıtıcı mevcut olandan farklıysa, yürütme askıya alınır, blok ilgili iş parçacığı havuzuna gönderilir ve tamamlandıktan sonra — orijinal bağlamla devam eder.
withContext, coroutine'in zaten üzerinde çalıştığı aynı dağıtıcı ile çağrıldığında, Kotlin hızlı yolu (fast-path) etkinleştirir: blok, DispatchedContinuation oluşturmadan ve iş parçacığı havuzuna göndermeden eşzamanlı olarak yürütülür. Bu, withContext'i aynı bağlamla tekrarlanan çağrılar için neredeyse ücretsiz hale getirir. JetBrains kıyaslamalarına (kotlinx.coroutines 1.8) göre, hızlı yol 0,1 µs'den daha kısa sürede tamamlanır.
Farklı bir dağıtıcı ile her withContext çağrısı yeni bir DispatchedContinuation oluşturur ve iş parçacığı değiştirme gerektirir — bu, yüke bağlı olarak 1 ila 5 µs sürer. Çoğu uygulama için bu gecikme fark edilmez, ancak binlerce yinelemeli döngüler içinde, işlemleri tek bir withContext bloğunda toplamak faydalıdır.
Deneyimli geliştiriciler bile withContext ile çalışırken hatalar yapar. En yaygın dört sorunu ve bunları önleme yollarını inceleyelim.
Geliştiriciler genellikle işlemleri tek bir blokta birleştirmek yerine her satırı ayrı bir withContext'e sarar. Farklı bir dağıtıcı ile yapılan her ekstra çağrı ek yük oluşturur.
Doğrusu: sıralı IO işlemlerini tek bir withContext(Dispatchers.IO) { ... } içinde birleştirin. Bazı işlemler CPU yoğunsa — aynı blok içinde withContext(Dispatchers.Default) kullanın.
withContext kodu sırayla yürütür. İki bağımsız ağ isteği tek bir withContext'e sarılmışsa, birbiri ardına çalışırlar. Paralellik için async + await kullanın.
// Sıralı — yavaş
withContext(Dispatchers.IO) {
val a = api.fetchA()
val b = api.fetchB()
}
// Paralel — hızlı
coroutineScope {
val a = async { api.fetchA() }
val b = async { api.fetchB() }
println("${a.await()} ${b.await()}")
}
withContext sırasında bir coroutine iptal edilirse, Dispatchers.IO üzerindeki blok da kesintiye uğrar. Ne pahasına olursa olsun tamamlanması gereken işlemler için (veritabanı yazma, analitik gönderme), withContext'i NonCancellable ile birleştirin.
withContext(Dispatchers.IO) içinde asla View bileşenlerini güncellemeyin. withContext, tüm blok tamamlanana kadar Main'e dönmez. UI güncellemelerini withContext'in kapanış parantezinden sonra yerleştirin — o zaman coroutine zaten ana iş parçacığında olacaktır.
Sıkça sorulan sorular
withContext, iş parçacığını bloke etmeyen, mevcut bir coroutine içinde bağlamı değiştiren bir askıya alma fonksiyonudur. runBlocking, coroutine'ler ile normal kod arasında bir köprüdür ve tamamlanana kadar mevcut iş parçacığını bloke eder. withContext UI iş parçacığı için güvenlidir, runBlocking değildir.
Hayır, withContext bir suspend fonksiyonudur, bu nedenle yalnızca başka bir suspend fonksiyonundan veya bir coroutine'den (launch/async) çağrılabilir. Normal bir fonksiyondan withContext çağrılamaz — bunun için runBlocking veya CoroutineScope gerekir.
Kotlin hızlı yolu (fast-path) etkinleştirir — blok, aynı iş parçacığında değişiklik olmadan eşzamanlı olarak yürütülür. Ek yük 0,1 µs'den azdır. Bu bir hata değildir, ancak böyle bir çağrı gereksizdir — kodu withContext olmadan yürütmek daha iyidir.
withContext içindeki istisnalar, normal koddaki gibi yayılır — try-catch aracılığıyla. Blok bir istisna fırlatırsa, üst coroutine'e yayılır ve işlenmezse onu iptal eder. withContext içinde veya etrafında try-catch kullanın.
Hayır, withContext yeni bir coroutine oluşturmaz. Mevcut coroutine'i kullanır ancak bağlamını geçici olarak değiştirir. Bu, onu alt coroutine'ler oluşturan launch ve async'den ayırır. Bu davranış, kotlinx.coroutines kaynak kodu tarafından onaylanmıştır.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun