Bỏ qua để đến nội dung

Dispatchers

Dispatchers quyết định coroutine sẽ chạy trên thread nào. Hiểu rõ dispatchers giúp bạn tối ưu hiệu năng và tránh lỗi.

Dispatcher Thread Pool Use Case
Dispatchers.Main Main/UI thread Update UI
Dispatchers.IO Shared pool (64+ threads) Network, File, Database
Dispatchers.Default Shared pool (CPU cores) CPU-intensive work
Dispatchers.Unconfined Inherit from caller Testing, special cases

Chạy trên Main/UI thread. Dùng để update UI.

viewModelScope.launch(Dispatchers.Main) {
// Update UI components
textView.text = "Hello"
progressBar.visibility = View.GONE
}

Optimization để tránh dispatch không cần thiết:

// Main: Luôn dispatch (queue vào main looper)
launch(Dispatchers.Main) {
// ...
}
// Main.immediate: Nếu đã ở Main thread, chạy ngay
launch(Dispatchers.Main.immediate) {
// Không cần queue nếu đã ở main thread
}

Chạy trên shared thread pool được optimize cho I/O operations.

suspend fun loadFromNetwork(): Data {
return withContext(Dispatchers.IO) {
// Network call
api.getData()
}
}
suspend fun readFile(): String {
return withContext(Dispatchers.IO) {
// File I/O
File("data.txt").readText()
}
}
suspend fun queryDatabase(): List<User> {
return withContext(Dispatchers.IO) {
// Database query
database.userDao().getAll()
}
}
  • Có thể scale lên 64+ threads (hoặc số cores, tùy cái nào lớn hơn)
  • Share threads với Default dispatcher
  • Tự động scale dựa trên workload

Chạy trên shared thread pool được optimize cho CPU-intensive work.

suspend fun processImage(bitmap: Bitmap): Bitmap {
return withContext(Dispatchers.Default) {
// Heavy computation
applyFilters(bitmap)
}
}
suspend fun sortLargeList(items: List<Item>): List<Item> {
return withContext(Dispatchers.Default) {
items.sortedBy { it.priority }
}
}
suspend fun parseJson(json: String): Data {
return withContext(Dispatchers.Default) {
// JSON parsing có thể tốn CPU
Json.decodeFromString(json)
}
}
  • Số threads = số CPU cores (hoặc 2, tùy cái nào lớn hơn)
  • Không nên dùng cho I/O (sẽ block thread pool)

Không giới hạn thread - chạy trên thread của caller cho đến suspension point đầu tiên.

launch(Dispatchers.Unconfined) {
println("Before: ${Thread.currentThread().name}") // Thread của caller
delay(100)
println("After: ${Thread.currentThread().name}") // Có thể khác thread!
}
  • Testing (với TestCoroutineDispatcher)
  • Cases đặc biệt cần inherit thread
❌ Không dùng trong production code
❌ Không dùng với UI operations

withContext chuyển sang dispatcher khác và quay về sau khi hoàn thành.

suspend fun loadAndProcess(): Result {
// Giả sử đang ở Main thread
val data = withContext(Dispatchers.IO) {
// -> Chuyển sang IO thread
api.fetchData()
}
// <- Quay về Main thread
val processed = withContext(Dispatchers.Default) {
// -> Chuyển sang Default thread
heavyProcessing(data)
}
// <- Quay về Main thread
return processed
}
class UserRepository(
private val api: ApiService,
private val dao: UserDao,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) {
suspend fun getUser(id: Int): User {
return withContext(ioDispatcher) {
// Tất cả network/database ở IO thread
val cached = dao.getUser(id)
if (cached != null) {
return@withContext cached
}
val remote = api.getUser(id)
dao.insertUser(remote)
remote
}
}
}

Tạo dispatcher riêng với thread pool tùy chỉnh:

// Single thread dispatcher
val singleThreadDispatcher = Executors.newSingleThreadExecutor()
.asCoroutineDispatcher()
// Fixed thread pool
val fixedThreadDispatcher = Executors.newFixedThreadPool(4)
.asCoroutineDispatcher()
// Sử dụng
launch(singleThreadDispatcher) {
// Luôn chạy trên 1 thread cố định
}
// Đừng quên close khi không cần!
singleThreadDispatcher.close()
  • Thread-confined resources (SQLite, Realm)
  • Rate limiting
  • Priority queues

class SearchViewModel(
private val repository: SearchRepository,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) : ViewModel() {
fun search(query: String) {
viewModelScope.launch { // Mặc định: Main
_state.value = SearchState.Loading
try {
// Fetch data từ network
val results = withContext(ioDispatcher) {
repository.search(query)
}
// Filter và sort (CPU work)
val processed = withContext(defaultDispatcher) {
results
.filter { it.isValid }
.sortedByDescending { it.relevance }
}
// Update UI (đã ở Main thread)
_state.value = SearchState.Success(processed)
} catch (e: Exception) {
_state.value = SearchState.Error(e.message)
}
}
}
}
class SearchViewModelTest {
private val testDispatcher = StandardTestDispatcher()
@Test
fun `search returns results`() = runTest {
val repository = FakeSearchRepository()
val viewModel = SearchViewModel(
repository = repository,
ioDispatcher = testDispatcher, // Inject test dispatcher
defaultDispatcher = testDispatcher
)
viewModel.search("kotlin")
advanceUntilIdle() // Đợi tất cả coroutines
assertTrue(viewModel.state.value is SearchState.Success)
}
}

Dispatcher Threads Use For
Main 1 (UI) Update UI
IO 64+ Network, File, Database
Default CPU cores Heavy computation
Unconfined Variable Testing only
  1. UI operationsDispatchers.Main
  2. Network/File/DatabaseDispatchers.IO
  3. Tính toán nặngDispatchers.Default
  4. Inject dispatchers để dễ test
  5. Dùng withContext để chuyển dispatcher trong suspend function

Học về Coroutine Scope - quản lý lifecycle của coroutines.