CoroutineScope là một interface Kotlin xác định vòng đời của một coroutine và cung cấp ngữ cảnh để khởi chạy các coroutine mới. Theo tài liệu Kotlin, 2025, mỗi phiên bản CoroutineScope chứa một CoroutineContext và quản lý tất cả các coroutine được khởi chạy bên trong nó. Khi scope bị hủy (cancel), tất cả coroutine con tự động bị hủy, ngăn chặn rò rỉ bộ nhớ.
Những điểm chính
CoroutineScope là một interface cơ bản từ thư viện kotlinx.coroutines, hoạt động như một container cho các coroutine. Nó xác định ranh giới vòng đời của coroutine: khi scope hoàn thành, tất cả coroutine bên trong nó tự động bị hủy.
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
Interface chỉ chứa một trường — coroutineContext. Thông qua nó, scope cung cấp một bộ điều phối (Dispatcher), một công việc (Job), một trình xử lý ngoại lệ và các phần tử ngữ cảnh khác cho tất cả coroutine được khởi chạy bên trong nó.
Tất cả các hàm khởi chạy coroutine — launch, async, runBlocking — đều là các hàm mở rộng trên CoroutineScope. Điều này có nghĩa là chúng chỉ có thể được gọi khi có đối tượng scope. Thiết kế này đảm bảo rằng mỗi coroutine có một cha và vòng đời được xác định rõ ràng.
Trong Android, mỗi thành phần kiến trúc có scope riêng: viewModelScope cho ViewModel, lifecycleScope cho Activity/Fragment. Trong ứng dụng máy chủ, scope có thể được gắn với một yêu cầu HTTP hoặc một pool kết nối cơ sở dữ liệu.
Hiểu hoạt động bên trong của CoroutineScope đòi hỏi sự quen thuộc với khái niệm Job và nguyên tắc đồng thời có cấu trúc.
Mỗi coroutine khi khởi chạy trả về một đối tượng Job (hoặc Deferred cho async). Job đại diện cho một nhiệm vụ với vòng đời hữu hạn: New, Active, Completing, Completed, Cancelling, Cancelled. Các đối tượng Job tạo thành cấu trúc cây:
Đồng thời có cấu trúc là một nguyên tắc kiến trúc chính của Kotlin Coroutines, nơi vòng đời của coroutine được gắn với vòng đời của scope. Điều này trái ngược với mô hình “bắn và quên”, nơi coroutine tiếp tục tồn tại sau khi scope hoàn thành. Lợi ích của đồng thời có cấu trúc:
Khi scope.cancel() được gọi, Job của scope chuyển sang trạng thái Cancelled, điều này hủy đệ quy tất cả Job con. Sau khi hủy, scope chỉ có thể được sử dụng lại nếu tạo một phiên bản CoroutineScope mới.
Bạn có thể tạo CoroutineScope thông qua hàm factory hoặc bằng cách implement interface trong lớp của mình. Hãy xem xét cả hai cách tiếp cận.
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
scope.launch {
println("Running on ${Thread.currentThread().name}")
}
Hàm factory nhận một CoroutineContext và tạo scope với ngữ cảnh được chỉ định. Ví dụ sử dụng Dispatchers.Default cho các tác vụ tốn CPU và SupervisorJob, giúp cách ly các ngoại lệ giữa các coroutine con.
class MyRepository {
private val scope = CoroutineScope(Dispatchers.IO + Job())
suspend fun fetchData(): Data = scope.async {
api.getData()
}.await()
fun cleanup() {
scope.cancel()
}
}
Chúng ta lưu scope như một trường của lớp và gọi thủ công cleanup để hủy nó. Cách tiếp cận này phù hợp với các thành phần có vòng đời được quản lý — ví dụ, repository hoặc trình quản lý.
Kotlin cho phép ủy quyền implement CoroutineScope thông qua từ khóa by:
class DataLoader : CoroutineScope by CoroutineScope(Dispatchers.IO) {
fun load() {
launch {
// coroutine runs in DataLoader scope
}
}
}
Cách tiếp cận này thuận tiện khi lớp tự nó là một scope và muốn cung cấp các phương thức khởi chạy coroutine. Tuy nhiên, hãy cẩn thận: lớp kế thừa tất cả các phương thức của CoroutineScope, bao gồm cancel, điều này có thể phá vỡ tính đóng gói.
GlobalScope là một CoroutineScope singleton cho toàn bộ ứng dụng. Việc sử dụng nó trong mã sản xuất không được khuyến nghị chính thức.
JetBrains chỉ cho phép GlobalScope trong các kịch bản hiếm: quy trình nền ở cấp ứng dụng cần tồn tại ngay cả sau khi đóng tất cả Activity (ví dụ: đồng bộ dữ liệu, phân tích). Nhưng ngay cả trong những trường hợp này, tốt hơn nên tạo scope riêng của bạn với CoroutineScope(SupervisorJob()).
Luôn sử dụng CoroutineScope tùy chỉnh với quản lý vòng đời rõ ràng. Trong Android, đó là viewModelScope và lifecycleScope. Trong ứng dụng máy chủ, hãy tạo scope cho mỗi yêu cầu hoặc pool kết nối.
Cả hai hàm đều là hàm tạm dừng (suspend) tạo scope tạm thời cho các tác vụ song song, nhưng hành vi của chúng với ngoại lệ khác nhau về cơ bản.
| Đặc điểm | coroutineScope | supervisorScope |
|---|---|---|
| Hành vi khi lỗi | Ngoại lệ trong coroutine con hủy tất cả các coroutine khác | Ngoại lệ trong coroutine con KHÔNG hủy các coroutine khác |
| Lan truyền lỗi | Có, ngoại lệ đầu tiên được lan truyền ra ngoài | Có, ngoại lệ đầu tiên được lan truyền ra ngoài |
| Job mặc định | Job() — con gắn với cha | SupervisorJob() — con không phụ thuộc lẫn nhau |
| Trường hợp sử dụng điển hình | Hoạt động nguyên tử nhiều bước | Tác vụ song song độc lập (tải UI) |
Sử dụng coroutineScope khi nhiều hoạt động song song tạo thành một hoạt động nguyên tử duy nhất. Ví dụ: tải dữ liệu từ ba máy chủ: nếu một yêu cầu thất bại, các yêu cầu còn lại vô nghĩa.
suspend fun loadProductPage(): ProductPage = coroutineScope {
val product = async { api.getProduct() }
val reviews = async { api.getReviews() }
ProductPage(product.await(), reviews.await())
}
Nếu getProduct hoặc getReviews ném ngoại lệ — cả hai coroutine đều bị hủy và ngoại lệ lan truyền đến mã gọi.
Sử dụng supervisorScope khi các hoạt động song song không phụ thuộc lẫn nhau. Ví dụ: tải dữ liệu hồ sơ trong nhiều phần độc lập: nếu phần đề xuất thất bại, tiêu đề hồ sơ và danh sách bạn bè vẫn được hiển thị.
Hãy xem xét những lỗi phổ biến nhất của nhà phát triển khi sử dụng CoroutineScope trong Kotlin.
Kịch bản rò rỉ coroutine phổ biến nhất là tạo scope mà không gọi cancel khi thành phần kết thúc. Nếu scope không bị hủy, coroutine tiếp tục chạy, giữ tham chiếu đến các đối tượng. Trong Android, hãy sử dụng viewModelScope hoặc lifecycleScope, tự động bị hủy.
GlobalScope bỏ qua vòng đời thành phần Android. Một coroutine được khởi chạy trong GlobalScope sau khi Activity đóng sẽ tiếp tục thực thi và cố gắng cập nhật UI — gây ra sự cố. Luôn sử dụng lifecycleScope cho các thành phần UI.
Sau khi gọi cancel(), scope không thể được tái sử dụng — tất cả coroutine bên trong nó đã hoàn thành. Tạo một phiên bản CoroutineScope mới thông qua hàm factory. Job() không hỗ trợ kích hoạt lại.
Khi ủy quyền với by, lớp có được một phương thức cancel() công khai có thể được gọi từ bất kỳ đâu, phá vỡ tính đóng gói. Lưu scope như một trường riêng tư thay vì ủy quyền interface.
Câu hỏi thường gặp
CoroutineScope là một interface sở hữu CoroutineContext và chịu trách nhiệm về vòng đời coroutine. CoroutineContext là một tập hợp các phần tử (bộ điều phối, job, trình xử lý lỗi) xác định coroutine thực thi “như thế nào”. Một điểm khác biệt: scope tạo coroutine, trong khi ngữ cảnh kiểm soát hành vi của chúng.
Có, đó là một mẫu tiêu chuẩn: CoroutineScope(Dispatchers.IO + SupervisorJob()). SupervisorJob ngăn chặn việc hủy theo tầng của các coroutine con khi một trong số chúng ném ngoại lệ. Điều này hữu ích cho các tác vụ song song độc lập mà lỗi ở một tác vụ không nên dừng các tác vụ khác.
Không có giới hạn về số lượng coroutine trong một scope — chúng chỉ bị giới hạn bởi bộ nhớ khả dụng và cài đặt bộ điều phối. Giới hạn thực tế thường là hàng nghìn coroutine hoạt động trong một scope. Tuy nhiên, số lượng lớn coroutine có thể chỉ ra vấn đề kiến trúc.
Cách đúng đắn là truyền scope vào lớp thông qua hàm tạo hoặc sử dụng runBlockingTest / runTest từ kotlinx-coroutines-test. Trong kiểm thử, bạn có thể thay thế scope bằng TestCoroutineDispatcher và kiểm soát việc thực thi coroutine theo cách thủ công.
Không, scope là một container bên ngoài cho coroutine. Bản thân coroutine không phải là scope. Tuy nhiên, bên trong một coroutine, bạn có thể tạo scope mới thông qua coroutineScope hoặc supervisorScope để khởi chạy coroutine con song song.
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