Skip to Content

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.

Các Dispatchers có sẵn

DispatcherThread PoolUse Case
Dispatchers.MainMain/UI threadUpdate UI
Dispatchers.IOShared pool (64+ threads)Network, File, Database
Dispatchers.DefaultShared pool (CPU cores)CPU-intensive work
Dispatchers.UnconfinedInherit from callerTesting, special cases

1. Dispatchers.Main

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 }

Main.immediate

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 }

2. Dispatchers.IO

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 điểm:

  • 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

3. Dispatchers.Default

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) } }

Đặc điểm:

  • 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)

4. Dispatchers.Unconfined

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! }

Khi nào dùng?

  • Testing (với TestCoroutineDispatcher)
  • Cases đặc biệt cần inherit thread

⚠️ Cảnh báo

❌ Không dùng trong production code ❌ Không dùng với UI operations

withContext - Chuyển Dispatcher

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 }

Pattern phổ biến trong Repository

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 } } }

Custom Dispatcher

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()

Use cases cho custom dispatcher:

  • Thread-confined resources (SQLite, Realm)
  • Rate limiting
  • Priority queues

Ví dụ tổng hợp

ViewModel với multiple dispatchers

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) } } } }

Testing với custom dispatcher

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) } }

📝 Tóm tắt

DispatcherThreadsUse For
Main1 (UI)Update UI
IO64+Network, File, Database
DefaultCPU coresHeavy computation
UnconfinedVariableTesting only

Quy tắc vàng

  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

Tiếp theo

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

Last updated on