Async/Await là một cặp từ khóa để viết mã bất đồng bộ theo phong cách đồng bộ, có sẵn trong Swift từ iOS 13 và trong Kotlin với coroutine. Theo Apple Swift Documentation, 2026, Async/Await thay thế chuỗi callback và GCD, làm cho mã bất đồng bộ trở nên tuyến tính và dễ đọc. Từ khóa async đánh dấu một hàm là bất đồng bộ, và await tạm dừng thực thi của nó cho đến khi nhận được kết quả.
Các Điểm Chính
Async/Await là một cấu trúc ngôn ngữ cho phép viết mã bất đồng bộ tuyến tính như mã đồng bộ. Trong Swift, nó xuất hiện trong iOS 13 / macOS 10.15 cùng với framework Swift Concurrency. Trong Kotlin, async/await có sẵn thông qua thư viện coroutine (kotlinx.coroutines) từ phiên bản 1.3.
Trước async/await, mã bất đồng bộ được xây dựng dựa trên callback, GCD hoặc RxSwift. Chuỗi callback lồng nhau dẫn đến địa ngục callback — sự lồng ghép sâu làm phức tạp việc đọc và gỡ lỗi. Async/Await giải quyết vấn đề này bằng cách cho phép các hàm bất đồng bộ “tạm dừng” tại các điểm await và tiếp tục sau khi nhận được kết quả.
Theo Google (2025), sử dụng async/await trong Kotlin giảm số dòng mã bất đồng bộ từ 40–60% so với cách tiếp cận callback. Trong Swift, tỷ lệ áp dụng Swift Concurrency đạt 68% trong số các ứng dụng được xuất bản năm 2025. iOS 16 đã thêm hỗ trợ Swift Concurrency trong UIKit, SwiftUI và Foundation, biến async/await thành công cụ tiêu chuẩn cho tất cả các dự án Apple mới.
Sự khác biệt chính giữa async/await và luồng là tạm dừng mà không chặn. Khi một hàm gặp await, nó tạm dừng, giải phóng luồng hiện tại cho các tác vụ khác. Sau khi hoàn thành thao tác bất đồng bộ, hàm tiếp tục trên cùng một luồng hoặc một luồng khác. Điều này được gọi là đa nhiệm cộng tác.
Một hàm async được biên dịch thành một máy trạng thái — một ôtômát hữu hạn quản lý các trạng thái thực thi. Tại điểm await, trình biên dịch lưu ngữ cảnh (biến cục bộ, địa chỉ trả về) và chuyển quyền kiểm soát cho runtime. Khi kết quả sẵn sàng, runtime khôi phục ngữ cảnh và tiếp tục thực thi.
Trong Swift, máy trạng thái này được triển khai ở cấp độ trình biên dịch SIL (Swift Intermediate Language). Trong Kotlin, cơ chế Continuation Passing Style (CPS) được sử dụng: mỗi hàm suspend chấp nhận một tham số Continuation ẩn qua đó kết quả được trả về. Cả hai cách tiếp cận đều đảm bảo tiếp tục an toàn mà không rò rỉ bộ nhớ.
Cách tiếp cận callback yêu cầu truyền một closure được gọi sau khi hoàn thành thao tác. Với ba yêu cầu tuần tự, bạn có ba mức lồng ghép với xử lý lỗi ở mỗi mức. Async/Await biến ba yêu cầu đó thành ba dòng tuần tự với một khối catch duy nhất.
GCD (DispatchQueue) giải quyết địa ngục callback thông qua hàng đợi nối tiếp và DispatchGroup, nhưng vẫn dài dòng. Cho một tác vụ đơn giản — tải người dùng, sau đó bạn bè, sau đó UI — cần tạo hàng đợi, nhóm và khối notify. Async/Await thực hiện cùng một tác vụ với ba dòng trong một hàm duy nhất.
| Khía cạnh | Callback | GCD | Async/Await |
|---|---|---|---|
| Lồng ghép | Sâu (kim tự tháp diệt vong) | Trung bình (nhóm+thông báo) | Tuyến tính (phẳng) |
| Xử lý lỗi | Theo từng callback | Thủ công | Hợp nhất (try/catch) |
| Hủy bỏ | Thủ công | Hạn chế | Task.cancel() |
| An toàn luồng | Thủ công | Qua hàng đợi nối tiếp | Qua MainActor |
Theo Apple WWDC 2024, Swift Concurrency async/await là cách tiếp cận được khuyến nghị cho mã mới. GCD vẫn được sử dụng để tích hợp với thư viện C và các kịch bản xử lý luồng cấp thấp cụ thể.
Task là đơn vị cơ bản của công việc bất đồng bộ trong Swift. Một Task được tạo trong ngữ cảnh của một actor hiện có hoặc trên một luồng bất kỳ. Bên trong Task, có thể gọi các hàm async thông qua await. TaskGroup cho phép khởi chạy nhiều tác vụ con song song và thu thập kết quả của chúng — tương tự DispatchGroup được nâng cấp.
Actor là một kiểu tham chiếu an toàn luồng bảo vệ trạng thái của nó khỏi các cuộc đua dữ liệu. Trình biên dịch đảm bảo rằng việc truy cập vào thuộc tính của actor chỉ có thể thực hiện qua các lệnh gọi async hoặc trong phạm vi cô lập của actor. Actor thay thế DispatchQueue nối tiếp để bảo vệ trạng thái có thể thay đổi dùng chung mà không cần khóa thủ công.
Nguyên tắc của Structured Concurrency phát biểu: mỗi tác vụ bất đồng bộ có cha, và cha không hoàn thành cho đến khi tất cả con hoàn thành. Nếu tác vụ A khởi chạy Task { await B() }, thì A đợi B. Điều này ngăn rò rỉ tác vụ và đảm bảo vòng đời có thể dự đoán được.
Trong Kotlin, async/await được triển khai thông qua coroutine — các luồng nhẹ của ngôn ngữ. Một hàm được đánh dấu bằng từ khóa suspend (tương tự async trong Swift). Để khởi chạy, sử dụng launch (bắn rồi quên) hoặc async (có kết quả). Await được gọi để lấy kết quả từ async.
Dispatcher xác định coroutine chạy trên nhóm luồng nào: Dispatchers.Main cho UI, Dispatchers.IO cho mạng/đĩa và Dispatchers.Default cho các tác vụ tốn CPU. Structured Concurrency được đảm bảo thông qua CoroutineScope — khi phạm vi bị hủy, tất cả coroutine con đều bị hủy.
Theo JetBrains (2025), 97% ứng dụng Android trên Google Play sử dụng kotlinx.coroutines và 82% trong số đó sử dụng async/await cho các yêu cầu mạng. Coroutine đã trở thành tiêu chuẩn thực tế cho lập trình bất đồng bộ trong Android. Kotlin Multiplatform cũng hỗ trợ coroutine, cho phép sử dụng mã bất đồng bộ dùng chung trên Android, iOS và phía máy chủ.
Hãy xem hai ví dụ cho mỗi ngôn ngữ: tải dữ liệu tuần tự và yêu cầu song song với TaskGroup hoặc async/await.
await tạm dừng thực thi cho đến khi nhận được dữ liệu người dùng, sau đó cho đến khi nhận được bạn bè. Không có closure lồng nhau.
func loadProfile() async throws -> Profile {
let user = try await api.fetchUser()
let friends = try await api.fetchFriends(for: user.id)
return Profile(user: user, friends: friends)
}
TaskGroup khởi chạy các tác vụ song song và thu thập kết quả vào một mảng. Thứ tự kết quả có thể không khớp với thứ tự khởi chạy.
func loadParallel() async throws -> [String] {
await withThrowingTaskGroup(of: String.self) { group in
group.addTask { try await api.fetchName() }
group.addTask { try await api.fetchEmail() }
group.addTask { try await api.fetchAvatar() }
var results = [String]()
for try await value in group { results.append(value) }
return results
}
}
Các hàm suspend có thể gọi các hàm suspend khác. Tương tự Swift: gọi tuần tự.
suspend fun loadProfile(): Profile {
val user = api.fetchUser()
val friends = api.fetchFriends(user.id)
return Profile(user, friends)
}
Trong Kotlin, tính song song đạt được thông qua coroutineScope với async cho mỗi yêu cầu.
suspend fun loadParallel(): List<String> = coroutineScope {
val name = async { api.fetchName() }
val email = async { api.fetchEmail() }
val avatar = async { api.fetchAvatar() }
listOf(name.await(), email.await(), avatar.await())
}
Quên Task { } — một hàm async không thể được gọi từ ngữ cảnh đồng bộ mà không bọc trong Task. Trong Swift, điều này biên dịch với lỗi. Trong Kotlin, yêu cầu khởi chạy qua lifecycleScope hoặc viewModelScope. Cố gắng gọi hàm suspend từ hàm thông thường sẽ không biên dịch được.
Không phải mọi hàm đều cần bất đồng bộ. Các tác vụ tốn CPU không có I/O không được hưởng lợi từ async — tốt hơn nên thực thi chúng trên DispatchQueue với QoS .userInitiated. Async/Await được tối ưu hóa cho các hoạt động I/O: mạng, đĩa, chờ đợi.
Sử dụng .result trên luồng chính (chờ đồng bộ kết quả của hàm async) dẫn đến chặn. Trong Swift, Task.synchronousWait không được Apple khuyến nghị. Trong Kotlin, runBlocking trên luồng chính là phản mẫu — hãy sử dụng lifecycleScope.
Nếu Task không được lưu trong thuộc tính của lớp, sau khi rời khỏi ngữ cảnh, nó sẽ bị hủy. Trong Swift, Task có phạm vi — vòng đời của nó gắn với ngữ cảnh tạo ra. Hãy lưu Task vào thuộc tính nếu bạn cần một thao tác dài hạn.
Các Câu Hỏi Thường Gặp
Async/Await là cấu trúc ngôn ngữ không yêu cầu tạo hàng đợi. DispatchQueue là API hệ thống để quản lý luồng. Async/await được biên dịch thành máy trạng thái ở cấp ngôn ngữ, GCD — thành syscall ở cấp hệ điều hành.
Không — await tạm dừng hàm nhưng giải phóng luồng cho các tác vụ khác. Sau khi hoàn thành thao tác, hàm tiếp tục trên bất kỳ luồng khả dụng nào từ nhóm.
Có — qua Task.cancel() trong Swift hoặc Job.cancel() trong Kotlin. Việc hủy lan truyền đến tất cả tác vụ con theo Structured Concurrency. Mã kiểm tra cờ hủy qua Task.isCancelled hoặc ensureActive().
MainActor là actor có thuộc tính và phương thức luôn thực thi trên luồng chính. Bằng cách đánh dấu hàm với @MainActor, bạn đảm bảo cập nhật UI xảy ra trên đúng luồng, thay thế DispatchQueue.main.async.
Async/Await có sẵn trong Swift (iOS 13+), Kotlin (qua kotlinx.coroutines), Dart (Flutter), JavaScript/TypeScript, Python, C#, Rust và Go (qua goroutines với cú pháp đường).
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