withContext — функција за пребацивање контекста извршења унутар корутине, која привремено мења нит или диспечера за наведени блок кода и враћа резултат назад у оригинални контекст. Према подацима JetBrains, 2025, withContext је један од најчешће коришћених алата корутина за рад са мрежним захтевима и диск операцијама. Функција гарантује да ће након завршетка блока корутина наставити извршење на оригиналном диспечеру, што спречава случајне грешке безбедности нити.
Главне тачке
withContext — суспендујућа функција из пакета kotlinx.coroutines која извршава прослеђени блок кода у задатом CoroutineContext-у и враћа резултат назад у оригинални контекст. Сигнатура функције изгледа овако:
suspend fun withContext (
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T
Параметар context прихвата било који CoroutineContext — најчешће један од стандардних Dispatchers.IO, Dispatchers.Default или Dispatchers.Main. Блок се извршава управо у том контексту, а резултат се враћа тамо одакле је withContext позван.
Након завршетка ламбде, withContext гарантовано пребацује извршење назад на оригинални диспечер. То значи да програмер не мора ручно да позива withContext(Dispatchers.Main) након позадинске операције — повратак се дешава аутоматски. Ово понашање је утврђено у спецификацији Котлин Coroutines од верзије 1.3.
Андроид развој — главна област примене withContext. Типичан сценарио: ViewModel покреће корутину на главној нити, унутар ње се позива withContext(Dispatchers.IO) за мрежни захтев, а резултат се након аутоматског повратка на Main користи за ажурирање UI. Овај приступ лежи у основи архитектуре MVVM и препоручује га Google у званичном водичу за корутине.
Да бисте разумели withContext, потребно је да упознате CoroutineContext и његову кључну компоненту — диспечера (Dispatcher). Свака корутина има скуп елемената контекста, међу којима диспечер одређује на којој нити или групи нити се извршава код.
| Диспечер | Намена | Величина групе |
|---|---|---|
| Dispatchers.Main | Главна UI нит (Android, JavaFX, Swing) | 1 (главна нит) |
| Dispatchers.IO | Диск и мрежне операције | 64 нити (лимит расте) |
| Dispatchers.Default | CPU-интензивна израчунавања | max(2, број језгара) |
| Dispatchers.Unconfined | Без фиксне нити | неограничен |
Важно је разумети да withContext не ствара нову корутину — он само мења контекст за постојећу. Ово је кључна разлика у односу на launch и async, који генеришу нове корутине. Унутрашња имплементација withContext-а је оптимизована: ако се тражени контекст поклапа са тренутним, пребацивање се не дешава — функција се извршава на истом диспечеру.
Dispatchers.Main унутар withContext(Dispatchers.Main) не изазива пребацивање — Котлин Coroutines препознаје идентичност контекста и прескаче непотребну операцију. Слично томе, withContext(Dispatchers.Default) унутар корутине која већ ради на Default не ствара додатни overhead. Ова оптимизација је имплементирана у ContinuationInterceptor-у.
Почетници често мешају withContext са launch и async, јер све три функције раде са корутинама и контекстом. Међутим, њихова намена је суштински различита.
| Карактеристика | withContext | launch | async |
|---|---|---|---|
| Ствара нову корутину | Не | Да | Да |
| Враћа резултат | Да (T директно) | Не (Job) | Да (Deferred<T>) |
| Извршење | Секвенцијално | Паралелно | Паралелно |
| Чекање резултата | Аутоматско | join() | await() |
| Типичан use-case | Промена диспечера | Fire-and-forget | Паралелна израчунавања |
Ако треба да извршите једну операцију у позадинској нити и добијете резултат — користите withContext. Ако треба да покренете неколико независних операција паралелно — користите async са await. Ако резултат није потребан (логовање, писање кеша) — launch. Google препоручује withContext као пожељан алат за Repository слој у Андроид архитектури.
Размотримо три практична сценарија коришћења withContext у Андроид апликацијама на Котлину. Сваки пример показује конкретан задатак и правилан образац.
ViewModel позива метод репозиторијума из корутине на Main. Унутар withContext(Dispatchers.IO) извршава се HTTP захтев, а резултат се аутоматски враћа:
class UserRepository(
private val api: UserApi
) {
suspend fun getUser(id: String): User {
return withContext(Dispatchers.IO) {
api.fetchUser(id)
}
}
}
Корутина у ViewModel-у позива getUser исто као обичну суспендујућу функцију — без експлицитног навођења диспечера. withContext скрива детаље пребацивања нити.
Када треба извршити више IO операција једну за другом, withContext их обједињује у један блок. Ово је ефикасније него умотавати сваку операцију у посебан withContext:
suspend fun loadUserProfile(id: String): Profile {
return withContext(Dispatchers.IO) {
val user = api.fetchUser(id)
val posts = api.fetchPosts(id)
Profile(user, posts)
}
}
Обе операције се извршавају на Dispatchers.IO, а Profile резултат се креира и враћа без непотребних пребацивања контекста. Ако су операције независне, боље је користити async за паралелно извршење.
У неким сценаријима потребно је извршити код који се не може отказати — на пример, чување стања при затварању екрана. Комбинација withContext + NonCancellable решава овај задатак:
withContext(Dispatchers.IO + NonCancellable) {
cache.saveState(state)
analytics.logEvent("state_saved")
}
Оператор + комбинује два елемента контекста: IO диспечер и NonCancellable флаг. Блок се извршава чак и ако је родитељска корутина отказана — ово је корисно за операције финализације.
Унутрашња имплементација withContext-а се ослања на механизам Continuation — централну апстракцију Котлин корутина. Свака тачка суспензије (suspend point) чува стање извршења у Continuation објекту, а withContext није изузетак.
Котлин компајлер преводи withContext у позив методе withContext из kotlinx.coroutines, која унутра ствара нову инстанцу DispatchedContinuation. Овај објекат обавија оригинални Continuation и замењује диспечера у њему. Ако се нови диспечер разликује од тренутног, извршење се суспендује, блок се шаље у одговарајућу групу нити, а након завршетка — наставља са оригиналним контекстом.
Када се withContext позива са истим диспечером на ком корутина већ ради, Котлин активира fast-path: блок се извршава синхроно, без стварања DispatchedContinuation и без слања у групу нити. Ово чини withContext практично бесплатним при поновљеним позивима са истим контекстом. Према JetBrains бенчмарковима (kotlinx.coroutines 1.8), fast-path се извршава за мање од 0,1 μs.
Сваки позив withContext са различитим диспечером ствара нови DispatchedContinuation и захтева пребацивање нити — ово траје од 1 до 5 μs у зависности од оптерећења. За већину апликација ово кашњење је неприметно, али у петљама са хиљадама итерација боље је агрегирати операције у један withContext блок.
Чак и искусни програмери праве грешке при раду са withContext. Размотримо четири најчешћа проблема и начине њиховог спречавања.
Програмери често умотавају сваки ред у посебан withContext, уместо да комбинују операције у један блок. Сваки додатни позив са другачијим диспечером ствара overhead.
Исправно: комбинујте секвенцијалне IO операције у један withContext(Dispatchers.IO) { ... }. Ако је део операција CPU-интензиван — користите withContext(Dispatchers.Default) унутар истог блока.
withContext извршава код секвенцијално. Ако су два независна мрежна захтева умотана у један withContext, они ће се извршавати један за другим. За паралелизам користите async + await.
// Секвенцијално — споро
withContext(Dispatchers.IO) {
val a = api.fetchA()
val b = api.fetchB()
}
// Паралелно — брзо
coroutineScope {
val a = async { api.fetchA() }
val b = async { api.fetchB() }
println("${a.await()} ${b.await()}")
}
Ако се корутина откаже током withContext-а, блок на Dispatchers.IO се такође прекида. За операције које морају да се заврше по сваку цену (упис у базу, слање аналитике), комбинујте withContext са NonCancellable.
Никада не ажурирајте View компоненте унутар withContext(Dispatchers.IO). withContext се не враћа на Main до завршетка целог блока. Ажурирање UI извршите након затварајуће заграде withContext — тада ће корутина већ бити на главној нити.
Често постављана питања
withContext — суспендујућа функција која не блокира нит, већ пребацује контекст унутар постојеће корутине. runBlocking — мост између корутина и обичног кода, који блокира тренутну нит до завршетка. withContext је безбедан за UI нит, runBlocking — није.
Не, withContext је суспендујућа функција, па се може позвати само из друге суспендујуће функције или из корутине (launch/async). Из обичне функције withContext се не позива — за то је потребан runBlocking или CoroutineScope.
Котлин активира fast-path — блок се извршава синхроно на истој нити без пребацивања. Overhead је мањи од 0,1 μs. Ово није грешка, али је такав позив сувишан — боље је једноставно извршити код без withContext-а.
Изузеци унутар withContext-а се преносе исто као у обичном коду — кроз try-catch. Ако блок баци изузетак, он се шири у родитељску корутину и отказује је ако није обрађен. Користите try-catch унутар withContext-а или око њега.
Не, withContext не ствара нову корутину. Користи постојећу корутину, али привремено мења њен контекст. То га разликује од launch и async, који стварају корутине потомке. Понашање је потврђено изворним кодом kotlinx.coroutines.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође