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 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.
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ế 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.
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.
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.
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ản | Khả năng áp dụng | Rủi ro |
|---|---|---|
| main() của ứng dụng console | Có | Không — đó là điểm vào, luồng không chặn UI |
| Kiểm thử JUnit | Có | Tối thiểu — kiểm thử đồng bộ theo định nghĩa |
| Luồng UI Android | Không | ANR, lag, đóng băng giao diện |
| Callback → Coroutine | Có, thận trọng | Chặ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ử.
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.
// 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.
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.
// 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ã.
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.
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
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.
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 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 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.
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
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.
Đọc thêm