CoroutineScope — nó là gì, vòng đời và hoạt động trong coroutine

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

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 — một interface với một trường CoroutineContext duy nhất, xác định vòng đời của coroutine
  • Job — một phần tử ngữ cảnh chịu trách nhiệm hủy bỏ: hủy scope sẽ hủy tất cả coroutine con
  • Đồng thời có cấu trúc — nguyên tắc mà coroutine con được gắn với scope cha
  • GlobalScope — scope toàn ứng dụng không được khuyến nghị do nguy cơ rò rỉ bộ nhớ
  • supervisorScope — scope đặc biệt nơi hủy một coroutine con không hủy các coroutine khác

CoroutineScope trong Kotlin là gì?

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.

kotlin
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ó.

Vai trò trong thư viện kotlinx.coroutines

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.

CoroutineScope được sử dụng ở đâu

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.

CoroutineScope hoạt động như thế nào: Job và đồng thời có cấu trúc

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.

Job — nhiệm vụ của coroutine

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:

  • Job cha — scope nơi coroutine được khởi chạy
  • Job con — mỗi coroutine được khởi chạy qua launch/async
  • Hủy cha → hủy tất cả con
  • Ngoại lệ trong con → hủy cha (ngoại trừ supervisorScope)

Nguyên tắc đồng thời có cấu trúc

Đồ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:

  • Vòng đời có thể dự đoán — khi scope hoàn thành, tất cả coroutine được đảm bảo dừng lại
  • Xử lý lỗi tự động — ngoại lệ trong bất kỳ coroutine con nào lan truyền đến scope
  • Không rò rỉ bộ nhớ — không có coroutine nào tiếp tục chạy sau khi scope hoàn thành
  • Phân cấp rõ ràng — mã phản ánh cấu trúc logic của các hoạt động song song

Vòng đời của CoroutineScope

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.

Tạo và cấu hình CoroutineScope

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.

Hàm factory CoroutineScope()

kotlin
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.

Implement interface thông qua composition

kotlin
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ý.

Implement thông qua ủy quyền

Kotlin cho phép ủy quyền implement CoroutineScope thông qua từ khóa by:

kotlin
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 so với CoroutineScope tùy chỉnh

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.

Vấn đề với GlobalScope

  • Thiếu đồng thời có cấu trúc — coroutine trong GlobalScope không gắn với vòng đời thành phần
  • Rò rỉ bộ nhớ — coroutine có thể tiếp tục chạy sau khi Activity/Fragment đóng
  • Khó kiểm thử — GlobalScope không thể thay thế trong kiểm thử
  • Tiêu thụ tài nguyên không kiểm soát — nhiều coroutine có thể chạy lâu hơn dự kiến

Khi nào GlobalScope được chấp nhận

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()).

Khuyến nghị

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.

coroutineScope so với supervisorScope: sự khác biệt

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ểmcoroutineScopesupervisorScope
Hành vi khi lỗiNgoại lệ trong coroutine con hủy tất cả các coroutine khácNgoại lệ trong coroutine con KHÔNG hủy các coroutine khác
Lan truyền lỗiCó, ngoại lệ đầu tiên được lan truyền ra ngoàiCó, ngoại lệ đầu tiên được lan truyền ra ngoài
Job mặc địnhJob() — con gắn với chaSupervisorJob() — con không phụ thuộc lẫn nhau
Trường hợp sử dụng điển hìnhHoạt động nguyên tử nhiều bướcTác vụ song song độc lập (tải UI)

Khi nào chọn coroutineScope

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.

kotlin
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.

Khi nào chọn supervisorScope

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ị.

Lỗi thường gặp khi làm việc với CoroutineScope

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.

Lỗi 1: Quên hủy scope

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.

Lỗi 2: Sử dụng GlobalScope trong Activity hoặc Fragment

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.

Lỗi 3: Tái sử dụng scope đã hủy

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.

Lỗi 4: Ủy quyền interface CoroutineScope không đúng

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 khác CoroutineContext như thế nào?

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ó thể tạo CoroutineScope với SupervisorJob khô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.

CoroutineScope có thể chứa bao nhiêu coroutine?

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.

Làm thế nào để kiểm thử mã với CoroutineScope?

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.

Một coroutine có thể có scope riêng khô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

  • CoroutineScope — một interface với trường coroutineContext, xác định vòng đời của coroutine được khởi chạy bên trong nó
  • Đồng thời có cấu trúc — hủy scope tự động hủy tất cả coroutine con, ngăn rò rỉ bộ nhớ
  • Job và SupervisorJob — hai chế độ xử lý lỗi: hủy theo tầng (Job) và lỗi cách ly (SupervisorJob)
  • GlobalScope — không được khuyến nghị cho sản xuất do thiếu ràng buộc vòng đời
  • coroutineScope so với supervisorScope — hoạt động song song nguyên tử so với tác vụ song song độc lập
  • viewModelScope và lifecycleScope — scope có sẵn cho Android, tự động hủy khi thành phần kết thúc
  • Hàm factory — cách ưa thích để tạo scope qua CoroutineContext + gọi cancel rõ ràng

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