Async/Await: bản chất, hàm async và làm việc với await

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

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à cú pháp đường cho mã bất đồng bộ không có callback lồng nhau
  • async đánh dấu một hàm là bất đồng bộ và có thể tạm dừng
  • await tạm dừng thực thi hàm cho đến khi hoàn thành thao tác bất đồng bộ
  • Task trong Swift tạo một đơn vị công việc bất đồng bộ mới với ngữ cảnh
  • Structured Concurrency đảm bảo tất cả tác vụ con hoàn thành trước tác vụ cha

Async/Await và lập trình bất đồng bộ là gì?

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.

Nguyên lý hoạt động: hàm có thể tạm dừng

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.

Cách Async/Await hoạt động: tạm dừng và tiếp 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ớ.

  • Swift — trình biên dịch dịch hàm async thành SIL với runAsync coroutine frame
  • Kotlin — mỗi hàm suspend nhận một Continuation và ủy quyền thực thi cho bộ điều phối
  • Cả hai ngôn ngữ — giải phóng luồng tại await, không chặn nó trong khi chờ đợi
  • Ngoại lệ — được truyền qua cơ chế tiêu chuẩn (throws / try-catch)

Async/Await vs Callback và GCD: so sánh cách tiếp cận

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ạnhCallbackGCDAsync/Await
Lồng ghépSâ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ỗiTheo từng callbackThủ côngHợp nhất (try/catch)
Hủy bỏThủ côngHạn chếTask.cancel()
An toàn luồngThủ côngQua hàng đợi nối tiếpQua 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ể.

Swift Concurrency: Task, TaskGroup và Actor

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.

Structured Concurrency

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.

Async/Await trong Kotlin với coroutine

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

Ví dụ mã với Async/Await trong Swift và Kotlin

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.

Swift: Gọi async tuần tự

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.

swift
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)
}

Swift: Yêu cầu song song với TaskGroup

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.

swift
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
    }
}

Kotlin: Gọi tuần tự với async/await

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

kotlin
suspend fun loadProfile(): Profile {
    val user = api.fetchUser()
    val friends = api.fetchFriends(user.id)
    return Profile(user, friends)
}

Kotlin: Yêu cầu song song với async/await

Trong Kotlin, tính song song đạt được thông qua coroutineScope với async cho mỗi yêu cầu.

kotlin
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())
}

Lỗi thường gặp và hạn chế của Async/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.

Sử dụng async quá mức không cần thiết

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.

Chặn luồng chính qua .result

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.

Rò rỉ Task khi hủy bỏ

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 khác gì so với DispatchQueue?

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.

await có chặn luồng không?

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ó thể hủy một tác vụ async không?

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 trong Swift là gì?

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.

Những ngôn ngữ nào có async/await?

Async/Await có sẵn trong Swift (iOS 13+), Kotlin (qua kotlinx.coroutines), Dart (Flutter), JavaScript/TypeScript, Python, C#, RustGo (qua goroutines với cú pháp đường).

Tổng Kết

  • Async/Await là cấu trúc ngôn ngữ cho mã bất đồng bộ tuyến tính không có địa ngục callback
  • Swift Concurrency cung cấp Task, TaskGroup, Actor và Structured Concurrency
  • Coroutine trong Kotlin sử dụng hàm suspend, Dispatcher và phạm vi coroutine
  • Tạm dừng không chặn là sự khác biệt chính của async/await so với luồng và hàng đợi
  • Structured Concurrency đảm bảo tất cả tác vụ con hoàn thành trước tác vụ cha
  • Async/Await vs GCD — cái trước ưu tiên cho mã mới, cái sau cho mã cũ và cấp thấp
  • MainActor trong Swift và Dispatchers.Main trong Kotlin bảo vệ luồng UI khỏi thao tác nền

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