Dispatchers — khái niệm chính, các loại bộ điều phối và cách hoạt động

Tác giả: IT Sectr Đã đăng: 2026-06-22 Thời gian đọc: 9 phút

Dispatchers trong Kotlin Coroutines là thành phần của CoroutineContext xác định luồng thực thi cho các coroutine: Main (luồng UI), IO (mạng và đĩa), Default (tác vụ tốn CPU) và Unconfined (luồng hiện tại). Mỗi bộ điều phối quản lý một nhóm luồng chuyên biệt được tối ưu hóa cho một loại công việc cụ thể. Theo hướng dẫn của JetBrains, 2024, việc chọn đúng bộ điều phối rất quan trọng đối với hiệu suất và độ ổn định của ứng dụng.

Những Điểm Chính

  • Dispatchers — bộ điều phối coroutine được xác định trước quản lý việc phân phối tác vụ giữa các luồng
  • Dispatchers.Main — luồng UI Android, để cập nhật giao diện và làm việc với View
  • Dispatchers.IO — nhóm cho yêu cầu mạng, đọc/ghi tệp và DB (lên đến 64 luồng)
  • Dispatchers.Default — nhóm cho tác vụ tốn CPU dựa trên số lõi xử lý
  • Dispatchers.Unconfined — kế thừa luồng hiện tại, không chuyển đổi

Dispatchers là gì?

Dispatchers là các triển khai của giao diện CoroutineDispatcher, là các phần tử của CoroutineContext. Chúng xác định coroutine sẽ thực thi trên luồng hoặc nhóm luồng nào. Khi tạo coroutine qua launch hoặc async, bộ điều phối có thể được truyền dưới dạng tham số đầu tiên: launch(Dispatchers.IO) { ... }. Nếu không chỉ định bộ điều phối, nó được kế thừa từ CoroutineScope bên ngoài.

Kotlin cung cấp bốn bộ điều phối tích hợp: Main, IO, Default, Unconfined. Mỗi bộ điều phối sử dụng nhóm luồng riêng được tối ưu hóa cho một loại hoạt động cụ thể. Chọn đúng bộ điều phối quyết định hiệu suất ứng dụng: lựa chọn sai dẫn đến giật lag UI, lõi CPU nhàn rỗi hoặc sử dụng luồng không hiệu quả.

Bộ điều phốiNhóm luồngLuồng tối đaSử dụng
Dispatchers.MainMột (UI)1Cập nhật UI, LiveData, View
Dispatchers.IONhóm IO64 (limitedParallelism)Mạng, tệp, DB
Dispatchers.DefaultNhóm CPUN lõiSắp xếp, phân tích, tính toán
Dispatchers.UnconfinedLuồng hiện tạiKhông áp dụngHoạt động trung gian, kiểm thử

Dispatchers.Main: Luồng UI

Dispatchers.Main là bộ điều phối thực thi coroutine trên luồng chính Android. Nó được thiết kế cho các hoạt động liên quan đến giao diện: cập nhật TextView, gọi notifyDataSetChanged, làm việc với LiveData và StateFlow. Trong Android, bộ điều phối này được triển khai qua Handler (Looper.getMainLooper()).

kotlin
// Correct switch to Main for UI updates
viewModelScope.launch(Dispatchers.IO) {
    val data = repository.fetchData()
    withContext(Dispatchers.Main) {
        _uiState.value = data
    }
}

Nếu một coroutine đã ở trên bộ điều phối Main, withContext(Dispatchers.Main) bổ sung không tạo ra chi phí — bộ điều phối kiểm tra luồng hiện tại và bỏ qua việc chuyển đổi. withContext là cách ưa thích để chuyển đổi giữa các bộ điều phối.

Dispatchers.IO: Hoạt động Mạng và Đĩa

Dispatchers.IO là bộ điều phối được tối ưu hóa cho các hoạt động I/O: yêu cầu HTTP (Ktor, OkHttp), đọc và ghi tệp, làm việc với Room hoặc SQLDelight. Nó sử dụng nhóm 64 luồng theo mặc định, có thể mở rộng dưới tải. Mỗi yêu cầu I/O mới có thể tạo thêm một luồng cho đến khi đạt giới hạn.

Giới hạn Song song

Để kiểm soát số lượng hoạt động I/O đồng thời, hãy sử dụng limitedParallelism(). Hàm này tạo một bộ điều phối mới với giới hạn về số luồng song song, ngăn chặn sự cạn kiệt nhóm trong các hoạt động hàng loạt.

kotlin
val limitedIo = Dispatchers.IO.limitedParallelism(4)

// Load 100 files with limit of 4 concurrent operations
coroutineScope {
    val files = (1..100).map { index ->
        async(limitedIo) {
            downloadFile("file_$index")
        }
    }
    files.awaitAll()
}

Sử dụng bộ điều phối IO cho tất cả các hoạt động nơi coroutine dành thời gian chờ đợi (I/O-bound). Các tác vụ tốn CPU trên bộ điều phối IO không hiệu quả — chúng chiếm các luồng dành cho I/O, làm giảm thông lượng của hệ thống.

Dispatchers.Default: Tác vụ Tốn CPU

Dispatchers.Default là bộ điều phối cho các hoạt động tính toán tốn bộ xử lý: sắp xếp, lọc, phân tích JSON (Moshi, Kotlinx Serialization), xử lý hình ảnh, tính toán. Kích thước nhóm bằng số lõi xử lý (nhưng không dưới 2). Điều này đảm bảo sử dụng CPU tối đa mà không cần chuyển đổi ngữ cảnh.

kotlin
suspend fun processData(input: List<RawRecord>): List<ProcessedRecord> {
    return withContext(Dispatchers.Default) {
        input
            .parallelStream()
            .map { transform(it) }
            .toList()
    }
}

Không sử dụng Dispatchers.Default cho các hoạt động I/O — điều này sẽ chặn các luồng nhóm CPU có thể xử lý các tác vụ tính toán. Việc tách IO và Default cho phép sử dụng tối ưu tài nguyên hệ thống: luồng IO chờ I/O, luồng CPU liên tục bận với tính toán.

Dispatchers.Unconfined: Luồng Hiện tại

Dispatchers.Unconfined là bộ điều phối đặc biệt không ràng buộc coroutine với bất kỳ nhóm nào. Coroutine bắt đầu thực thi trong luồng nơi launch/async được gọi, và sau khi tạm dừng tiếp tục trong luồng đã gọi resume. Hành vi này phù hợp cho các hoạt động trung gian không yêu cầu ngữ cảnh cố định.

kotlin
fun main() = runBlocking {
    launch(Dispatchers.Unconfined) {
        println("Before delay: ${Thread.currentThread().getName()}")
        delay(500L)
        println("After delay: ${Thread.currentThread().getName()}")
    }
}

Trong mã sản xuất, Dispatchers.Unconfined hiếm khi được sử dụng. Các trường hợp sử dụng chính: chuyển đổi nhẹ trước khi truyền dữ liệu sang bộ điều phối khác và kiểm thử. Đối với khối lượng công việc sản xuất, hãy sử dụng bộ điều phối rõ ràng — Unconfined không thể dự đoán được vì luồng thực thi phụ thuộc vào cách triển khai resume.

Cách Chọn Bộ Điều Phối

Việc chọn bộ điều phối phụ thuộc vào loại tác vụ: hoạt động UI → Main, I/O-bound → IO, CPU-bound → Default, trung gian → kế thừa từ phạm vi. Đối với Android, nên khởi chạy coroutine trên bộ điều phối nơi công việc chính được thực hiện và chuyển sang Main qua withContext trước khi cập nhật UI.

  • Không chạy hoạt động I/O trên bộ điều phối Main — điều này chặn UI
  • Không chạy tác vụ tốn CPU trên bộ điều phối IO — điều này tiêu tốn không hiệu quả các luồng nhóm IO
  • Sử dụng limitedParallelism để kiểm soát đồng thời trong các hoạt động I/O hàng loạt
  • Chuyển đổi bộ điều phối bằng withContext, không phải tạo phạm vi mới
  • Kế thừa bộ điều phối từ phạm vi nếu coroutine không yêu cầu nhóm cụ thể

Đối với các kịch bản phức tạp, kết hợp các bộ điều phối bằng toán tử +: Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler. Điều này tạo ra CoroutineContext với bộ điều phối được chỉ định, xử lý lỗi và hệ thống phân cấp Job biệt lập.

Câu hỏi Thường gặp

Sự khác biệt giữa Dispatchers.IO và Dispatchers.Default là gì?

Dispatchers.IO sử dụng nhóm lên đến 64 luồng cho các hoạt động I/O-bound (chờ I/O), trong khi Dispatchers.Default sử dụng nhóm dựa trên số lõi CPU cho các tác vụ tính toán. Khi khan hiếm luồng, cả hai nhóm có thể chia sẻ luồng với nhau.

Tôi có thể tạo bộ điều phối riêng trong Kotlin Coroutines không?

Có, sử dụng newSingleThreadContext() cho một luồng đơn hoặc newFixedThreadPoolContext() cho nhóm cố định. Đối với sản xuất, hãy sử dụng limitedParallelism() dựa trên các bộ điều phối hiện có — điều này hiệu quả hơn so với tạo nhóm mới.

Điều gì xảy ra khi gọi Dispatchers.Main từ một luồng nền?

Nếu Dispatchers.Main không khả dụng (ví dụ: trong kiểm thử JUnit hoặc dịch vụ nền), một IllegalStateException sẽ được ném ra. Sử dụng TestCoroutineDispatcher cho kiểm thử và Dispatchers.IO hoặc Default cho dịch vụ nền.

Làm thế nào để giới hạn số lượng hoạt động I/O song song?

Sử dụng Dispatchers.IO.limitedParallelism(N), trong đó N là số luồng song song tối đa. Điều này ngăn chặn sự cạn kiệt nhóm trong các yêu cầu hàng loạt và cung cấp song song được kiểm soát.

Khi nào sử dụng Dispatchers.Unconfined?

Dispatchers.Unconfined phù hợp cho các hoạt động trung gian: chuyển đổi dữ liệu nhẹ trước khi chuyển sang bộ điều phối khác, kịch bản kiểm thử. Trong mã Android sản xuất, không được khuyến nghị do luồng thực thi không xác định sau khi tạm dừng.

Tóm tắt

  • Dispatchers — thành phần CoroutineContext xác định luồng thực thi coroutine
  • Dispatchers.Main — cho hoạt động UI Android, một luồng chính
  • Dispatchers.IO — cho hoạt động mạng và đĩa, nhóm lên đến 64 luồng
  • Dispatchers.Default — cho tính toán tốn CPU, nhóm dựa trên số lõi
  • Dispatchers.Unconfined — không ràng buộc luồng, cho hoạt động trung gian
  • withContext — cơ chế chính để chuyển đổi giữa các bộ điều phối trong coroutine
  • Chọn sai bộ điều phối dẫn đến giật lag UI hoặc sử dụng tài nguyên không hiệu quả

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm