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 — 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.
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())
}
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.
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.
Ư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.
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.
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ự.
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ức | Mô tả | Suspend? |
|---|---|---|
| await() | Tạm dừng coroutine cho đến khi có kết quả | Có |
| 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 null | Không |
| isCompleted | Kiểm tra xem coroutine đã hoàn thành chưa | Khô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ệ.
suspend fun <T> loadAll(requests: List<suspend () -> T>): List<T> {
return coroutineScope {
val deferreds = requests.map { async { it() } }
deferreds.awaitAll()
}
}
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.
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.
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ủ.
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, 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.
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() — 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.
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ớ.
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
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