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
Phần tiêu đề “Các Dispatchers có sẵn”| 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 |
1. Dispatchers.Main
Phần tiêu đề “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
Phần tiêu đề “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 ngaylaunch(Dispatchers.Main.immediate) { // Không cần queue nếu đã ở main thread}2. Dispatchers.IO
Phần tiêu đề “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:
Phần tiêu đề “Đặ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
Phần tiêu đề “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:
Phần tiêu đề “Đặ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
Phần tiêu đề “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?
Phần tiêu đề “Khi nào dùng?”- Testing (với
TestCoroutineDispatcher) - Cases đặc biệt cần inherit thread
⚠️ Cảnh báo
Phần tiêu đề “⚠️ Cảnh báo”❌ Không dùng trong production code❌ Không dùng với UI operationswithContext - Chuyển Dispatcher
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “Custom Dispatcher”Tạo dispatcher riêng với thread pool tùy chỉnh:
// Single thread dispatcherval singleThreadDispatcher = Executors.newSingleThreadExecutor() .asCoroutineDispatcher()
// Fixed thread poolval fixedThreadDispatcher = Executors.newFixedThreadPool(4) .asCoroutineDispatcher()
// Sử dụnglaunch(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:
Phần tiêu đề “Use cases cho custom dispatcher:”- Thread-confined resources (SQLite, Realm)
- Rate limiting
- Priority queues
Ví dụ tổng hợp
Phần tiêu đề “Ví dụ tổng hợp”ViewModel với multiple dispatchers
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “📝 Tóm tắt”| Dispatcher | Threads | Use For |
|---|---|---|
| Main | 1 (UI) | Update UI |
| IO | 64+ | Network, File, Database |
| Default | CPU cores | Heavy computation |
| Unconfined | Variable | Testing only |
Quy tắc vàng
Phần tiêu đề “Quy tắc vàng”- UI operations →
Dispatchers.Main - Network/File/Database →
Dispatchers.IO - Tính toán nặng →
Dispatchers.Default - Inject dispatchers để dễ test
- Dùng
withContextđể chuyển dispatcher trong suspend function
Tiếp theo
Phần tiêu đề “Tiếp theo”Học về Coroutine Scope - quản lý lifecycle của coroutines.