Dispatchers i Kotlin Coroutines — komponenter i CoroutineContext som bestämmer trådar för exekvering av korutiner: Main (UI-tråd), IO (nätverk och disk), Default (CPU-intensiva uppgifter) och Unconfined (aktuell tråd). Varje dispatcher hanterar en specialiserad trådpool optimerad för en specifik typ av arbete. Enligt JetBrains guide, 2024 är valet av rätt dispatcher avgörande för applikationens prestanda och stabilitet.
Huvudpunkter
Dispatchers — implementationer av gränssnittet CoroutineDispatcher, som är element i CoroutineContext. De bestämmer på vilken tråd eller trådpool korutinen ska exekveras. När en korutin skapas via launch eller async kan dispatcher skickas som första parameter: launch(Dispatchers.IO) { ... }. Om ingen dispatcher anges ärvs den från den externa CoroutineScope.
Kotlin tillhandahåller fyra inbyggda dispatchers: Main, IO, Default, Unconfined. Varje dispatcher använder sin egen trådpool optimerad för en specifik typ av operation. Rätt val av dispatcher avgör applikationens prestanda: felaktigt val leder till fördröjningar i gränssnittet, overksamma CPU-kärnor eller ineffektiv trådförbrukning.
| Dispatcher | Trådpool | Max trådar | Användning |
|---|---|---|---|
| Dispatchers.Main | En (UI) | 1 | UI-uppdatering, LiveData, View |
| Dispatchers.IO | IO-pool | 64 (limitedParallelism) | Nätverk, filer, databaser |
| Dispatchers.Default | CPU-pool | N kärnor | Sortering, parsning, beräkningar |
| Dispatchers.Unconfined | Aktuell tråd | N/A | Mellanliggande operationer, tester |
Dispatchers.Main — dispatcher som utför korutiner på huvudtråden i Android. Den är avsedd för operationer relaterade till gränssnittet: uppdatera TextView, anropa notifyDataSetChanged, arbeta med LiveData och StateFlow. I Android är denna dispatcher implementerad via Handler (Looper.getMainLooper()).
// Korrekt växling till Main för UI-uppdateringar
viewModelScope.launch(Dispatchers.IO) {
val data = repository.fetchData()
withContext(Dispatchers.Main) {
_uiState.value = data
}
}
Om korutinen redan är på Main-dispatchern skapar extra withContext(Dispatchers.Main) ingen overhead — dispatchern kontrollerar den aktuella tråden och hoppar över växlingen. withContext är det föredragna sättet att växla mellan dispatchers.
Dispatchers.IO — dispatcher optimerad för in-/utmatningsoperationer: HTTP-förfrågningar (Ktor, OkHttp), läsning och skrivning av filer, arbete med Room eller SQLDelight. Den använder som standard en pool på 64 trådar, skalbar under belastning. Varje ny IO-förfrågan kan skapa en extra tråd tills gränsen nås.
För att kontrollera antalet samtidiga IO-operationer, använd limitedParallelism(). Denna funktion skapar en ny dispatcher med en begränsning av antalet parallella trådar, vilket förhindrar att poolen töms vid massiva operationer.
val limitedIo = Dispatchers.IO.limitedParallelism(4)
// Ladda 100 filer med en gräns på 4 samtidiga operationer
coroutineScope {
val files = (1..100).map { index ->
async(limitedIo) {
downloadFile("file_$index")
}
}
files.awaitAll()
}
Använd IO-dispatchern för alla operationer där korutinen tillbringar tid med att vänta (I/O-bound). CPU-intensiva uppgifter på IO-dispatchern är ineffektiva — de upptar trådar avsedda för in-/utmatning, vilket minskar systemets genomströmning.
Dispatchers.Default — dispatcher för beräkningsoperationer som belastar processorn: sortering, filtrering, JSON-parsning (Moshi, Kotlinx Serialization), bildbehandling, beräkningar. Poolens storlek är lika med antalet processorkärnor (men inte mindre än 2). Detta säkerställer maximal CPU-belastning utan kontextväxling.
suspend fun processData(input: List<RawRecord>): List<ProcessedRecord> {
return withContext(Dispatchers.Default) {
input
.parallelStream()
.map { transform(it) }
.toList()
}
}
Använd inte Dispatchers.Default för IO-operationer — detta blockerar trådarna i CPU-poolen som skulle kunna bearbeta beräkningsuppgifter. Uppdelningen i IO och Default möjliggör optimal användning av systemresurser: IO-trådar väntar på in-/utmatning, CPU-trådar är ständigt upptagna med beräkningar.
Dispatchers.Unconfined — en speciell dispatcher som inte binder korutinen till någon pool. Korutinen börjar exekveras i samma tråd där launch/async anropades och återupptas efter avstängning i den tråd som anropade resume. Detta beteende är lämpligt för mellanliggande operationer som inte kräver en fast kontext.
fun main() = runBlocking {
launch(Dispatchers.Unconfined) {
println("Före fördröjning: ${Thread.currentThread().getName()}")
delay(500L)
println("Efter fördröjning: ${Thread.currentThread().getName()}")
}
}
I produktionskod används Dispatchers.Unconfined sällan. Huvudfall: lätta transformationer innan data skickas till en annan dispatcher och tester. För produktionsbelastning, använd explicita dispatchers — Unconfined är oförutsägbar eftersom exekveringstråden beror på implementeringen av resume.
Valet av dispatcher bestäms av uppgiftstypen: UI-operationer → Main, I/O-bound → IO, CPU-bound → Default, mellanliggande → nedärvning från scope. För Android rekommenderas att starta korutinen på den dispatcher där huvudarbetet utförs och före UI-uppdatering växla till Main via withContext.
För komplexa scenarier, kombinera dispatchers med operatorn +: Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler. Detta skapar en CoroutineContext med specificerad dispatcher, felhantering och en isolerad Job-hierarki.
Vanliga frågor
Dispatchers.IO använder en pool på upp till 64 trådar för I/O-bound operationer (väntan på in-/utmatning), medan Dispatchers.Default använder en pool baserat på antalet CPU-kärnor för beräkningsuppgifter. Vid trådbrist kan båda poolerna dela trådar med varandra.
Ja, använd newSingleThreadContext() för en entrådad eller newFixedThreadPoolContext() för en fast pool. För produktion, använd limitedParallelism() baserat på befintliga dispatchers — detta är effektivare än att skapa nya pooler.
Om Dispatchers.Main inte är tillgänglig (t.ex. i ett JUnit-test eller bakgrundstjänst), kastas IllegalStateException. För tester, använd TestCoroutineDispatcher, för bakgrundstjänster — Dispatchers.IO eller Default.
Använd Dispatchers.IO.limitedParallelism(N), där N är det maximala antalet parallella trådar. Detta förhindrar att poolen töms vid massiva förfrågningar och ger kontrollerad parallellism.
Dispatchers.Unconfined är lämplig för mellanliggande operationer: lätta datatransformationer innan data skickas till en annan dispatcher, testscenarier. I Android-produktionskod rekommenderas den inte på grund av osäker exekveringstråd efter avstängning.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också