Coroutines là các luồng nhẹ trong Kotlin cho lập trình bất đồng bộ, có sẵn qua thư viện kotlinx.coroutines. Theo JetBrains Kotlin Documentation, 2026, Coroutines cho phép tạm dừng thực thi hàm mà không chặn luồng, không giống như Thread truyền thống. Coroutines chạy trên một nhóm luồng giới hạn, khiến chúng nhẹ hơn hàng nghìn lần so với luồng gốc. Kotlin Coroutines được tích hợp đầy đủ với Android Jetpack, Retrofit, Room và các thư viện phổ biến khác của hệ sinh thái Android.
Những Điểm Chính
Coroutines là một cơ chế lập trình bất đồng bộ trong Kotlin, được triển khai trong thư viện kotlinx.coroutines. Không giống như các luồng của hệ điều hành, coroutines không bị ràng buộc vào một luồng cụ thể: chúng có thể tạm dừng trên một luồng và tiếp tục trên một luồng khác. Một luồng đơn có thể thực thi hàng nghìn coroutines, chuyển đổi giữa chúng với chi phí tối thiểu.
Coroutines xuất hiện trong Kotlin 1.3 (2018) như một tính năng thử nghiệm và trở nên ổn định trong Kotlin 1.5 (2021). Coroutines giải quyết vấn đề callback hell tương tự như async/await, nhưng cung cấp API phong phú hơn: kênh (Channel), Flow, xử lý ngoại lệ trong hệ thống phân cấp Job và tích hợp trực tiếp với Android Lifecycle.
Theo JetBrains (2025), mỗi coroutine tiêu thụ khoảng 100 byte bộ nhớ so với 1+ MB cho một luồng gốc. Điều này cho phép chạy hàng triệu coroutines trong một ứng dụng duy nhất mà không có rủi ro OutOfMemoryError. Chính sự nhẹ nhàng của coroutines khiến chúng trở thành công cụ ưa thích cho bất đồng bộ trong Android.
Mỗi coroutine Kotlin được biên dịch thành một máy trạng thái thông qua Continuation Passing Style (CPS). Trình biên dịch thêm một tham số Continuation ẩn vào mỗi hàm suspend. Continuation chứa điểm tiếp tục và tất cả các biến cục bộ. Khi một coroutine tạm dừng, runtime lưu Continuation và khi nó tiếp tục, runtime khôi phục nó trên bất kỳ luồng khả dụng nào từ nhóm Dispatcher.
suspend là một từ khóa Kotlin đánh dấu hàm có thể tạm dừng. Hàm như vậy chỉ có thể được gọi từ một hàm suspend khác hoặc từ một coroutine. Bên trong một hàm suspend, bạn có thể gọi các hàm suspend khác theo bất kỳ thứ tự nào và mỗi điểm gọi là một điểm tạm dừng tiềm năng.
Cơ chế rất đơn giản: khi một hàm suspend gọi một hàm suspend khác, nó tạm dừng tại điểm đó, giải phóng luồng. Sau khi hàm được gọi hoàn thành, runtime tiếp tục thực thi từ vị trí đã lưu. Đây được gọi là hủy hợp tác (cooperative cancellation) — không có luồng nào bị chặn.
Quan trọng: hàm suspend không phải là bất đồng bộ theo mặc định. Thứ tự thực thi vẫn tuần tự nếu không sử dụng launch hoặc async. suspend chỉ cho phép hàm tạm dừng mà không chặn luồng và trở thành một phần của ngữ cảnh coroutine. Continuation Passing Style là một mô hình biên dịch trong đó mỗi hàm suspend nhận một callback Continuation ẩn và trình biên dịch tạo ra một máy trạng thái để quản lý các lần tạm dừng và tiếp tục.
CoroutineScope là một ngữ cảnh xác định vòng đời của coroutines. Tất cả coroutines phải được khởi chạy trong một scope. Khi một scope bị hủy (ví dụ: khi Activity kết thúc), tất cả các coroutine con của nó tự động bị hủy. Điều này ngăn chặn rò rỉ tác vụ nền. Android Jetpack cung cấp các scope có sẵn cho mỗi thành phần: viewModelScope cho ViewModel và lifecycleScope cho Activity và Fragment, tự động bị hủy khi thành phần tương ứng bị phá hủy.
Tương tranh có cấu trúc (Structured Concurrency) là một nguyên tắc đảm bảo rằng một coroutine sẽ không hoàn thành cho đến khi tất cả các coroutine con của nó hoàn thành. Hệ thống phân cấp Job tạo thành một cây: một coroutine gốc tạo ra job cha, các con tạo ra job con. Việc hủy job cha lan truyền đến tất cả các con. Structured Concurrency là một khác biệt cơ bản giữa coroutines và luồng.
| Scope | Nơi sử dụng | Hủy bỏ |
|---|---|---|
| GlobalScope | Chỉ cho tác vụ daemon | Không tự động hủy |
| viewModelScope | Android ViewModel | Khi ViewModel bị xóa |
| lifecycleScope | Android Activity/Fragment | Khi lifecycle bị phá hủy |
| coroutineScope | Bên trong hàm suspend | Khi job cha bị hủy |
Job thông thường hủy tất cả siblings khi một coroutine con thất bại. SupervisorJob là một ngoại lệ: lỗi trong một coroutine con không ảnh hưởng đến các coroutine khác. Điều này quan trọng khi nhiều tác vụ độc lập được thực thi song song và một trong số chúng có thể gặp lỗi mà không cần hủy các tác vụ khác.
Dispatchers xác định coroutines được thực thi trên luồng nào. Dispatchers.Main — luồng UI chính của Android. Dispatchers.IO — nhóm cho các hoạt động chặn (mạng, đĩa). Dispatchers.Default — cho các tác vụ tốn CPU. Dispatchers.Unconfined — bắt đầu trong luồng hiện tại nhưng không đảm bảo ở lại trên đó. Chọn Dispatcher phù hợp rất quan trọng cho hiệu suất: một tác vụ IO trên Default sẽ chặn nhóm tính toán, trong khi tác vụ CPU trên IO sẽ tạo ra các luồng không cần thiết.
withContext — một hàm để chuyển đổi Dispatcher bên trong coroutine. Ví dụ, một hàm suspend phân tích JSON có thể chuyển sang Dispatchers.Default để tính toán và quay lại Dispatchers.Main để cập nhật UI. withContext là trình xây dựng được sử dụng nhiều nhất trong phát triển Android.
launch — khởi chạy một coroutine, trả về Job, không trả về kết quả (fire-and-forget). async — khởi chạy một coroutine, trả về Deferred từ đó có thể lấy kết quả qua await. runBlocking — chặn luồng hiện tại để thực thi coroutine (chỉ cho kiểm thử và hàm main). Lựa chọn trình xây dựng phụ thuộc vào kịch bản: launch phù hợp cho các sự kiện và cập nhật, async cho các tác vụ có kết quả, runBlocking chỉ cho kiểm thử hoặc điểm vào.
Hãy xem xét ba kịch bản thực tế: một coroutine cơ bản với launch, một cuộc gọi song song với async và xử lý lỗi với SupervisorJob.
viewModelScope.launch khởi chạy một coroutine trong ngữ cảnh ViewModel. Khi ViewModel bị xóa, coroutine tự động bị hủy.
class ProfileViewModel : ViewModel() {
fun loadUser() {
viewModelScope.launch(Dispatchers.IO) {
val user = api.fetchUser()
withContext(Dispatchers.Main) {
showUser(user)
}
}
}
}
coroutineScope với async khởi chạy ba yêu cầu song song. Kết quả được thu thập qua .await(). Nếu bất kỳ yêu cầu nào thất bại, tất cả đều bị hủy.
suspend fun loadDashboard(): Dashboard = coroutineScope {
val user = async { api.fetchUser() }
val posts = async { api.fetchPosts() }
val stats = async { api.fetchStats() }
Dashboard(user.await(), posts.await(), stats.await())
}
SupervisorJob cho phép mỗi coroutine hoàn thành độc lập. Lỗi trong một yêu cầu không hủy các yêu cầu khác.
val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
scope.launch {
try { api.fetchUsers() } catch (e: Exception) { log(e) }
}
scope.launch {
try { api.fetchPosts() } catch (e: Exception) { log(e) }
}
Luồng (Thread) là một nguyên thủy của hệ điều hành. Mỗi luồng có ngăn xếp riêng (~1 MB) và yêu cầu một cuộc gọi hệ thống để tạo và chuyển đổi. Coroutines là một nguyên thủy ngôn ngữ, không bị ràng buộc với hệ điều hành. Chúng sử dụng Continuation để lưu trạng thái và chuyển đổi ở cấp độ runtime mà không cần cuộc gọi hệ thống.
Theo Google (2025), sử dụng coroutines thay vì luồng giảm mức tiêu thụ bộ nhớ cho các tác vụ nền trong ứng dụng Android từ 90–95%. Tất cả các thư viện Android hiện đại (Retrofit, Room, WorkManager) đều có hỗ trợ coroutine tích hợp thông qua các hàm suspend. Ktor (framework máy khách HTTP của JetBrains) cũng được xây dựng hoàn toàn trên coroutines, cung cấp các hàm suspend cho mỗi yêu cầu mà không cần API callback. Room hỗ trợ coroutines thông qua các hàm suspend trong DAO, cho phép thực thi truy vấn cơ sở dữ liệu mà không chặn luồng chính.
Luồng vẫn cần thiết cho mã gốc qua JNI, các cuộc gọi chặn CPU-intensive không giới hạn thời gian (kết xuất video, mô phỏng) và khi tích hợp với thư viện C. Đối với mọi thứ khác — coroutines.
Câu hỏi thường gặp
Coroutine là một đơn vị công việc có thể tạm dừng chạy trên một luồng hiện có. Luồng là một tài nguyên hệ thống với ngăn xếp riêng. Coroutines nhẹ hơn luồng hàng nghìn lần và không chặn tài nguyên khi tạm dừng.
Dispatchers.IO được thiết kế cho các hoạt động I/O chặn (mạng, tệp) và có thể tạo luồng mới khi cần. Dispatchers.Default có một nhóm kích thước cố định (số lõi CPU) cho các tính toán tốn CPU.
Job.cancel() hủy coroutine và tất cả các con của nó. Để kiểm tra hủy bên trong coroutine, sử dụng ensureActive() — nó ném CancellationException nếu coroutine bị hủy.
Có — thông qua thư viện kotlinx-coroutines-rx3. Nó cung cấp các hàm awaitSingle, awaitFirst và các hàm khác để chuyển đổi Observable/Single thành các hàm suspend và ngược lại qua flowable.
Flow là một luồng dữ liệu bất đồng bộ nguội, tương đương với RxJava Observable trong coroutines. Flow phát ra các giá trị tuần tự và kết thúc với một ngoại lệ hoặc thành công. Nó hỗ trợ map, filter, catch và các toán tử khác.
Tổng kế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