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
| 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
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 operationswithContext - 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
| 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
- 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
Học về Coroutine Scope - quản lý lifecycle của coroutines.
Last updated on