并发编程 — async/await

🚀 Swift 中 async/await 的完整指南 #

async/await 是 Swift 5.5 引入的异步编程模型,基于 Swift Concurrency,旨在替代传统的回调闭包(Closures)和 GCD(Grand Central Dispatch)写法。它可以让异步代码像同步代码一样简洁、可读,极大地简化了处理异步任务的复杂性。


🧩 1. async/await 的基本概念 #

  • async:用于声明异步函数,表示该函数内部可能会有异步操作(例如网络请求、I/O 等)。
  • await:用于调用异步函数,表示等待异步任务完成,获取其返回结果。

示例:基本使用 #

import Foundation

// 定义异步函数
func fetchData() async -> String {
    // 模拟耗时操作
    try? await Task.sleep(nanoseconds: 2 * 1_000_000_000) // 等待 2 秒
    return "Hello from async/await!"
}

// 调用异步函数
Task {
    let result = await fetchData()
    print(result)
}

解释: #

  • async 声明 fetchData 为异步函数。
  • await 表示等待异步任务完成,挂起当前任务,直至获取结果。
  • Task {} 用于在非异步上下文中调用异步函数(例如在 main.swift 或 SwiftUI 中的 Button)。

2. 异步函数的定义与调用 #

(1) 异步函数的定义 #

func loadUserData() async -> String {
    return "User Data Loaded"
}
  • async 放在 -> 前,表示函数为异步函数。
  • 支持任何返回类型,甚至可以结合 ResultOptional

(2) 异步函数的调用 #

在异步上下文中调用 #

func performTask() async {
    let data = await loadUserData()
    print(data)
}

在同步上下文中调用(使用 Task #

Task {
    let data = await loadUserData()
    print(data)
}
  • 如果你在 ButtononAppear 等事件中调用异步函数,需要使用 Task 包裹。

🌍 3. 异步网络请求示例 #

import Foundation

func fetchPost() async throws -> String {
    let url = URL(string: "https://jsonplaceholder.typicode.com/posts/1")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return String(data: data, encoding: .utf8) ?? "No Data"
}

Task {
    do {
        let post = try await fetchPost()
        print(post)
    } catch {
        print("Error: \(error)")
    }
}

关键点: #

  • try await:表示异步操作可能抛出错误,需使用 do-catch 捕获。
  • URLSession.shared.data(from:) 是支持 async/await 的异步方法。

🧵 4. 结构化并发(Structured Concurrency) #

(1) 并行执行多个异步任务 #

func fetchUser() async -> String {
    try? await Task.sleep(nanoseconds: 1_000_000_000)
    return "User Data"
}

func fetchPosts() async -> String {
    try? await Task.sleep(nanoseconds: 2_000_000_000)
    return "Posts Data"
}

Task {
    async let user = fetchUser()
    async let posts = fetchPosts()

    let result = await "\(await user), \(await posts)"
    print(result)
}

解释: #

  • async let:并行启动多个异步任务,提升性能。
  • await:等待任务完成,按需获取结果。

(2) 使用 TaskGroup 执行并发任务 #

func fetchItems() async -> [String] {
    await withTaskGroup(of: String.self) { group in
        for i in 1...3 {
            group.addTask {
                try? await Task.sleep(nanoseconds: UInt64(i) * 1_000_000_000)
                return "Item \(i)"
            }
        }
        
        var results = [String]()
        for await result in group {
            results.append(result)
        }
        return results
    }
}

Task {
    let items = await fetchItems()
    print(items)
}
  • withTaskGroup:创建任务组,管理多个异步任务。
  • for await:异步遍历任务组的结果,按任务完成的顺序返回。

5. 处理异步错误 #

(1) 使用 try awaitdo-catch #

func riskyTask() async throws -> String {
    let success = Bool.random()
    if success {
        return "Success!"
    } else {
        throw NSError(domain: "TaskError", code: 1, userInfo: nil)
    }
}

Task {
    do {
        let result = try await riskyTask()
        print(result)
    } catch {
        print("Error occurred: \(error.localizedDescription)")
    }
}
  • try await:用于可能抛出错误的异步函数。
  • do-catch:捕获并处理错误。

⚙️ 6. MainActor 确保主线程更新 UI #

在 SwiftUI 中,UI 更新必须在主线程上完成,@MainActor 可确保这一点:

@MainActor
func updateUI(_ text: String) {
    print("UI updated with: \(text)")
}

Task {
    let data = await fetchUser()
    await updateUI(data) // 确保 UI 更新在主线程上
}
  • @MainActor:将函数标记为主线程执行,确保线程安全。

🚀 7. async/await vs GCD vs Combine #

特性async/awaitGCD(DispatchQueue)Combine
代码可读性✅ 简洁、接近同步代码❌ 回调嵌套复杂⚠️ 流式编程,需适应思维模型
错误处理✅ 直接支持 try-catch❌ 需要手动处理错误catch 操作符处理错误
并发性能✅ 支持结构化并发,自动优化线程调度⚠️ 需要手动管理队列⚠️ 多线程管理需额外关注
UI 更新支持✅ 搭配 @MainActor 易于主线程更新⚠️ 需明确指定 DispatchQueue.main.async⚠️ 需使用 .receive(on:) 指定主线程
学习曲线🚀 容易上手🧠 易于初学者理解📊 较高,适合响应式编程

🎯 8. 最佳实践 #

  1. 异步任务管理:在 SwiftUI 中,使用 Task@MainActor 确保 UI 安全更新。
  2. 避免阻塞主线程:即使在异步函数中,也要避免使用 sleep() 等阻塞操作,推荐使用 Task.sleep()
  3. 错误处理:始终使用 try awaitdo-catch,确保错误被妥善处理。
  4. 结构化并发:当需要并行处理多个任务时,优先考虑 async letTaskGroup,提高性能。
  5. 资源释放:在适当时机取消不必要的异步任务,避免内存泄漏或不必要的资源占用。

🔑 总结 #

  • async/await 提供了简洁、高效的异步编程模型,极大地提高了代码可读性和可维护性。
  • 它与 Swift 的结构化并发模型深度集成,支持错误处理、任务取消和主线程更新等高级功能。
  • 在 SwiftUI 中,async/await 是处理异步数据加载、网络请求、后台任务的首选工具。
本文共 1601 字,创建于 Feb 5, 2025
相关标签: Swift, Xcode