async: nó là gì, Deferred và các coroutine song song hoạt động như thế nào

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

async — là Coroutine Builder trong Kotlin khởi chạy một coroutine và trả về đối tượng Deferred<T> để lấy kết quả của một thao tác bất đồng bộ. async cho phép thực hiện nhiều tác vụ song song và tổng hợp kết quả của chúng thông qua await(). Theo hướng dẫn của JetBrains, 2024, async là cách ưu tiên để tổ chức các tính toán song song trong coroutine.

Điểm chính

  • async — trình xây dựng coroutine trả về Deferred<T> với kết quả của thao tác bất đồng bộ
  • Deferred — một lời hứa bất đồng bộ, kế thừa Job, với phương thức await() để lấy kết quả
  • async cho phép thực hiện các tác vụ độc lập song song, giảm thời gian tổng thể
  • await() — một hàm suspend tạm dừng coroutine cho đến khi có kết quả mà không chặn
  • Đồng thời có cấu trúc với async đảm bảo hủy tất cả các tác vụ song song khi có lỗi

async trong Kotlin Coroutines là gì?

async — là một hàm mở rộng của CoroutineScope tạo ra một coroutine trả về kết quả. Khác với launch, async trả về Deferred<T> — một đối tượng đại diện cho một giá trị trong tương lai. Một coroutine được khởi chạy qua async thực hiện đồng thời với các coroutine khác, cho phép song song hóa các tính toán.

async được sử dụng khi kết quả của một thao tác bất đồng bộ cần thiết cho xử lý tiếp theo: tải dữ liệu từ nhiều nguồn, xử lý hàng loạt các bộ sưu tập, các yêu cầu song song đến nhiều API khác nhau. Deferred được tạo ngay lập tức và kết quả trở nên khả dụng sau khi coroutine hoàn thành.

kotlin
import kotlinx.coroutines.*

suspend fun String.delayedValue(): String {
    delay(1000L)
    return this
}

fun main() = runBlocking {
    val deferred: Deferred<String> = async { "Hello".delayedValue() }
    println(deferred.await())
}

Cú pháp async và Deferred

Chữ ký của async tương tự launch: các tham số CoroutineContext, CoroutineStart giống nhau và một khối suspend. Sự khác biệt nằm ở kiểu trả về — Deferred<T> thay vì Job, trong đó T là kiểu kết quả được trả về bởi biểu thức cuối cùng của khối.

kotlin
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T>

Deferred<T> — một interface kế thừa Job và thêm các phương thức await(), getCompleted(), getCompletionExceptionOrNull(). await() là một hàm suspend và chỉ có thể được gọi bên trong một coroutine hoặc một hàm suspend khác. getCompleted() là một phương thức không chặn ném ra một ngoại lệ nếu coroutine chưa hoàn thành.

Tác vụ song song với async

Ưu điểm chính của async là khả năng thực hiện các tác vụ song song. Thay vì chờ đợi từng thao tác một cách tuần tự (tổng thời gian = tổng các thời gian), async khởi chạy các coroutine đồng thời, giảm tổng thời gian xuống bằng thời gian của thao tác lâu nhất.

Tải dữ liệu song song

Một kịch bản điển hình — tải hồ sơ người dùng, cài đặt và lịch sử đơn hàng bằng ba yêu cầu async song song. Sau khi cả ba hoàn thành, kết quả được kết hợp thành một mô hình dữ liệu duy nhất.

kotlin
suspend fun loadUserDashboard(userId: String): Dashboard = coroutineScope {
    val profile = async { api.getProfile(userId) }
    val settings = async { api.getSettings(userId) }
    val orders = async { api.getRecentOrders(userId) }

    Dashboard(
        profile = profile.await(),
        settings = settings.await(),
        orders = orders.await()
    )
}

Hàm coroutineScope khởi chạy các coroutine con và chờ chúng hoàn thành trước khi trả về kết quả. Toàn bộ khối được thực hiện song song, không tuần tự.

Xử lý kết quả Deferred

Deferred hỗ trợ nhiều phương thức để làm việc với kết quả. Ngoài await(), còn có các phương thức để kiểm tra trạng thái, lấy giá trị đã hoàn thành và tổng hợp nhiều Deferred thông qua các hàm tiện ích của Kotlin Coroutines.

Phương thứcMô tảSuspend?
await()Tạm dừng coroutine cho đến khi có kết quả
getCompleted()Trả về kết quả nếu coroutine đã hoàn thành (nếu không thì Exception)Không
getCompletionExceptionOrNull()Trả về ngoại lệ khi có lỗi hoặc nullKhông
isCompletedKiểm tra xem coroutine đã hoàn thành chưaKhông

Để tổng hợp một danh sách các Deferred, awaitAll() được sử dụng — một hàm tạm dừng thực thi cho đến khi tất cả Deferred trong bộ sưu tập hoàn thành. Nếu ít nhất một kết thúc với lỗi, awaitAll() ném ngoại lệ.

kotlin
suspend fun <T> loadAll(requests: List<suspend () -> T>): List<T> {
    return coroutineScope {
        val deferreds = requests.map { async { it() } }
        deferreds.awaitAll()
    }
}

Sự khác biệt giữa async và launch

Việc lựa chọn giữa async và launch phụ thuộc vào việc có cần kết quả của coroutine hay không. launch trả về Job và phù hợp cho các thao tác fire-and-forget, async trả về Deferred và được sử dụng cho các tác vụ trả về dữ liệu. Về cú pháp và tham số, các trình xây dựng giống hệt nhau.

  • launch → Job (không có kết quả), async → Deferred<T> (kết quả T)
  • launch — tác vụ fire-and-forget (ghi log, lưu cache, gửi phân tích)
  • async — tính toán song song (tải từ nhiều API, xử lý hàng loạt)
  • Cả hai đều hỗ trợ đồng thời có cấu trúc và các tham số CoroutineStart
  • await() chỉ khả dụng cho Deferred, launch không cung cấp kết quả

Quy tắc quan trọng: không bao giờ sử dụng async cho fire-and-forget. Nếu kết quả của coroutine không cần thiết, hãy sử dụng launch. async tạo ra chi phí bổ sung do tạo Deferred, điều này không hợp lý trong các kịch bản như vậy.

Ví dụ async trong phát triển di động

Trong Android, async được sử dụng cho các thao tác song song trong ViewModel và UseCases. Các kịch bản điển hình: tải hồ sơ và bản tin cùng lúc, lấy thời tiết từ nhiều dịch vụ thời tiết, đồng bộ dữ liệu hàng loạt với máy chủ.

kotlin
class HomeViewModel : ViewModel() {
    fun loadHomeScreen() {
        viewModelScope.launch {
            val userData = async(Dispatchers.IO) { repository.getUserData() }
            val newsFeed = async(Dispatchers.IO) { repository.getNewsFeed() }
            val notifications = async(Dispatchers.IO) { repository.getNotifications() }

            val state = HomeState(
                user = userData.await(),
                news = newsFeed.await(),
                unread = notifications.await()
            )
            _uiState.update { state }
        }
    }
}

Nên bao bọc một nhóm async trong coroutineScope hoặc supervisorScope để cô lập lỗi. supervisorScope cho phép các coroutine async còn lại tiếp tục ngay cả khi một coroutine thất bại — hữu ích cho các thao tác không quan trọng.

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

Có thể sử dụng async mà không cần await không?

Được, nhưng vô ích. async không có await() khởi chạy một coroutine nhưng kết quả của nó bị mất. Nếu không cần kết quả, hãy sử dụng launch — nó không tạo Deferred và hiệu quả hơn về bộ nhớ và hiệu suất.

async xử lý ngoại lệ như thế nào?

Một ngoại lệ bên trong async được lưu trong Deferred. Gọi await() sẽ ném ngoại lệ này. Để kiểm tra mà không ném, hãy sử dụng getCompletionExceptionOrNull(). Khi sử dụng coroutineScope, một lỗi sẽ hủy tất cả các coroutine con.

awaitAll trong Kotlin Coroutines là gì?

awaitAll() — một hàm mở rộng cho Iterable<Deferred<T>> tạm dừng coroutine cho đến khi tất cả Deferred hoàn thành. Trả về một danh sách kết quả. Nếu ít nhất một Deferred thất bại, nó ném ngoại lệ và hủy các coroutine còn lại.

async khác gì so với GlobalScope.async?

Có. async là một hàm mở rộng của CoroutineScope gắn với phạm vi. GlobalScope.async tạo một coroutine không gắn với vòng đời — nó có thể chạy vô hạn. GlobalScope không được khuyến nghị trong Android do nguy cơ rò rỉ bộ nhớ.

Có thể khởi chạy bao nhiêu coroutine async cho các yêu cầu song song?

Số lượng do bộ điều phối quyết định: Dispatchers.IO hỗ trợ tối đa 64 luồng đồng thời, Dispatchers.Default — tối đa số lõi CPU. Đối với các thao tác hàng loạt, hãy sử dụng song song giới hạn thông qua Semaphore hoặc mapNotNull với awaitAll.

Tổng kết

  • async — một trình xây dựng coroutine trả về Deferred<T> để lấy kết quả bất đồng bộ
  • Deferred kế thừa Job và thêm các phương thức await(), getCompleted(), getCompletionExceptionOrNull()
  • Thực thi song song với async giảm tổng thời gian xuống thời gian của thao tác lâu nhất
  • awaitAll() — một tiện ích để chờ tất cả Deferred trong một bộ sưu tập
  • coroutineScope — wrapper được khuyến nghị cho một nhóm async với đảm bảo hủy
  • Không sử dụng async cho fire-and-forget — thay vào đó hãy dùng launch
  • Trong Android, async được sử dụng để tải song song trong ViewModel với viewModelScope

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