runBlocking — định nghĩa, cầu nối chặn và cách hoạt động

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

runBlocking — một Coroutine Builder trong Kotlin chặn luồng hiện tại cho đến khi coroutine được truyền vào hoàn thành. Không giống như launch và async, nó không phải là hàm suspend và có thể được gọi từ mã thông thường (chặn). Theo tài liệu JetBrains, 2024, runBlocking đóng vai trò là cầu nối giữa thế giới đồng bộ và bất đồng bộ, cho phép chạy coroutine từ hàm main và kiểm thử.

Những điểm chính

  • runBlocking — một builder chặn tạo CoroutineScope và chờ coroutine hoàn thành
  • Chặn luồng — runBlocking giữ luồng hiện tại cho đến khi coroutine và tất cả con của nó hoàn thành
  • Điểm vào — main(), kiểm thử JUnit và cầu nối giữa mã chặn và bất đồng bộ
  • Cấm trên luồng chính Android — gọi runBlocking trên luồng UI gây ANR
  • Giải pháp thay thế — lifecycleScope, viewModelScope, TestCoroutineDispatcher cho Android

runBlocking là gì?

runBlocking là một hàm Kotlin tạo CoroutineScope mới và chạy coroutine được truyền vào, chặn luồng hiện tại cho đến khi nó hoàn thành hoàn toàn. Không giống như tất cả các Coroutine Builder khác, runBlocking không phải là hàm suspend và có thể được gọi từ mã đồng bộ thông thường. Chữ ký của runBlocking nhận CoroutineContext và một khối suspend, trả về kết quả kiểu T.

kotlin
public fun <T> runBlocking(
    context: CoroutineContext = EmptyCoroutineContext,
    block: suspend CoroutineScope.() -> T
): T

runBlocking khởi động một event-loop mới trên luồng hiện tại. Khi coroutine gọi một hàm suspend (ví dụ: delay() hoặc await()), runBlocking chặn luồng và thực thi các coroutine đã lên lịch khác trên cùng luồng cho đến khi coroutine bị tạm dừng được tiếp tục. Đây là chặn hợp tác — luồng không nhàn rỗi mà xử lý các coroutine khác.

Cách runBlocking hoạt động

Cơ chế nội bộ của runBlocking dựa trên event-loop: khi một hàm suspend được gọi, runBlocking tạm dừng thực thi khối hiện tại và chạy các coroutine khác từ hàng đợi. Khi hàm suspend hoàn thành, việc thực thi được tiếp tục. Chu kỳ này tiếp diễn cho đến khi tất cả các coroutine kết thúc.

Event-loop bên trong

runBlocking sử dụng nhóm đơn luồng riêng để thực thi coroutine. Không giống như Dispatchers.IO hoặc Default, runBlocking không chuyển luồng — nó xử lý tất cả coroutine trên luồng hiện tại, xen kẽ việc thực thi của chúng. Đây là builder duy nhất đảm bảo thực thi trên cùng một luồng.

kotlin
fun main() {
    val threadName = Thread.currentThread().getName()
    println("Trước runBlocking trên $threadName")

    val result = runBlocking {
        println("Bên trong runBlocking trên ${Thread.currentThread().getName()}")
        delay(500L)
        "Done"
    }

    println("Sau runBlocking: $result")
}

Đầu ra sẽ cho thấy cả ba câu lệnh println đều thực thi trên cùng một luồng. runBlocking không chuyển luồng mà tổ chức đa nhiệm hợp tác trong một luồng duy nhất bằng event-loop.

Khi nào sử dụng runBlocking

runBlocking được chứng minh trong ba kịch bản: điểm vào main() trong ứng dụng console, kiểm thử đơn vị hàm suspend và cầu nối — gọi mã suspend từ thư viện dựa trên callback hoặc chặn. Trong mã Android sản xuất, việc sử dụng trên luồng chính bị nghiêm cấm.

Kịch bảnKhả năng áp dụngRủi ro
main() của ứng dụng consoleKhông — đó là điểm vào, luồng không chặn UI
Kiểm thử JUnitTối thiểu — kiểm thử đồng bộ theo định nghĩa
Luồng UI AndroidKhôngANR, lag, đóng băng giao diện
Callback → CoroutineCó, thận trọngChặn nhóm luồng với thao tác dài

Cho kiểm thử Android, sử dụng kotlinx-coroutines-test với TestDispatcher thay vì runBlocking. Điều này cung cấp kiểm soát thời gian, dọn dẹp tự động và cách ly kiểm thử.

Giải pháp thay thế runBlocking

Trong hầu hết các kịch bản, runBlocking có thể và nên được thay thế bằng các giải pháp bất đồng bộ. Cho Android, đó là viewModelScope, lifecycleScope hoặc CoroutineScope với dispatcher phù hợp. Cho kiểm thử — TestCoroutineDispatcher và runTest.

kotlin
    // Xấu: runBlocking trên luồng chính Android
runBlocking(Dispatchers.Main) {
    val result = networkApi.fetchData()
    textView.setText(result)
}

// Tốt: lifecycleScope
lifecycleScope.launch {
    val result = withContext(Dispatchers.IO) { networkApi.fetchData() }
    textView.setText(result)
}

Cho kiểm thử, giải pháp thay thế là runTest từ thư viện kotlinx-coroutines-test. Nó tạo TestCoroutineScope với thời gian ảo, cho phép kiểm tra độ trễ mà không cần chờ thực tế. Điều này tăng tốc kiểm thử và làm chúng trở nên xác định.

Ví dụ sử dụng runBlocking

Kịch bản phổ biến nhất là kiểm tra hàm suspend. runBlocking trong kiểm thử cho phép chờ đồng bộ kết quả coroutine mà không thay đổi kiến trúc. Kịch bản thứ hai là thư viện với API callback, nơi các hàm suspend được gọi từ ngữ cảnh chặn qua runBlocking.

kotlin
// Kiểm tra hàm suspend với runBlocking
class RepositoryTest {
    @Test
    fun `fetchUser returns correct data`() {
        val repository = UserRepository(FakeApi())

        val result = runBlocking {
            repository.fetchUser("123")
        }

        assertEquals("John", result.name)
        assertEquals("john@test.com", result.email)
    }
}

Cho cầu nối giữa callback và thế giới suspend, sử dụng CompletableDeferred kết hợp với runBlocking thay vì callbacks — điều này đơn giản hóa chuỗi thao tác bất đồng bộ và cải thiện khả năng đọc mã.

Nguy hiểm của việc sử dụng sai

Sử dụng không đúng cách runBlocking là một trong những lỗi phổ biến khi chuyển từ phương pháp chặn sang coroutine. Vấn đề chính: gọi trên luồng chính Android, lồng runBlocking, sử dụng trong hàm bất đồng bộ và chạy thao tác dài qua runBlocking.

  • ANR — runBlocking trên luồng chính Android chặn kết xuất UI hơn 5 giây
  • Deadlock — runBlocking lồng nhau trong coroutine trên cùng luồng dẫn đến chặn lẫn nhau
  • Nhầm lẫn dispatcher — Dispatchers.Main trong runBlocking trên luồng nền không có Looper và ném ngoại lệ
  • Rò rỉ bộ nhớ — runBlocking không tự động bị hủy khi Activity/Fragment bị phá hủy

Quy tắc vàng: runBlocking là cầu nối, không phải giải pháp thay thế. Chỉ sử dụng nó để kết nối thế giới chặn và không chặn. Cho tất cả các tác vụ khác, sử dụng launch, async hoặc lifecycleScope.

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

Tại sao runBlocking chặn luồng trong khi các builder khác thì không?

runBlocking là builder duy nhất không phải là hàm suspend. Nó khởi động event-loop trên luồng hiện tại và không trả lại quyền điều khiển cho đến khi tất cả coroutine hoàn thành. launch và async trả lại quyền điều khiển ngay lập tức, thực thi coroutine trong nền.

Có thể sử dụng runBlocking trong Android ViewModel không?

Không khuyến khích. ViewModel có viewModelScope tích hợp tự động quản lý coroutine và hủy chúng khi bị phá hủy. runBlocking trong ViewModel chặn luồng và không phản hồi với việc hủy vòng đời.

Sử dụng gì thay vì runBlocking trong kiểm thử đơn vị?

Sử dụng runTest từ thư viện kotlinx-coroutines-test. Nó cung cấp TestCoroutineScope với kiểm soát thời gian ảo, hủy tự động và thực thi xác định.

Event-loop trong runBlocking là gì?

Event-loop là một chu kỳ xử lý sự kiện trong runBlocking. Khi một coroutine bị tạm dừng (ví dụ: delay()), event-loop chuyển sang thực thi các coroutine sẵn sàng khác trên cùng luồng. Điều này tạo ảo giác đa nhiệm mà không cần chuyển luồng.

Điều gì xảy ra khi gọi runBlocking bên trong runBlocking?

runBlocking lồng nhau trên cùng luồng tạo ra deadlock — khối ngoài chờ khối trong, nhưng khối trong không thể bắt đầu cho đến khi khối ngoài kết thúc. Trên các luồng khác nhau, điều này được cho phép nhưng không được khuyến khích do độ phức tạp gỡ lỗi.

Tóm tắt

  • runBlocking — một Coroutine Builder chặn, cầu nối giữa mã chặn và bất đồng bộ
  • Event-loop runBlocking xử lý coroutine hợp tác trên một luồng duy nhất không chuyển đổi
  • Kịch bản được phép — main(), kiểm thử JUnit, cầu nối từ thư viện callback
  • Kịch bản bị cấm — luồng UI Android, gọi lồng nhau, thao tác dài
  • Giải pháp thay thế — lifecycleScope, viewModelScope, runTest cho kiểm thử
  • Nguy cơ ANR — runBlocking trên luồng chính làm đóng băng ứng dụng sau 5 giây
  • Cho mã Android sản xuất, sử dụng builder bất đồng bộ — runBlocking không được thiết kế cho UI

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