Swift Concurrency (async/await) 的使用和概念,并且详情阐述actor 的意义和作用

Swift Concurrency (async/await) 是 Swift 5.5 引入的一套新的并发编程模型,旨在让异步代码的编写更加简单、安全、可读。它引入了 async/await 语法、结构化并发、Task 和 Actor 等概念。

1. async/await 基础

async 函数

使用 async 关键字标记一个函数是异步的,这意味着该函数可以挂起自己的执行,等待异步操作完成。

复制代码
swift

func fetchData(from url: URL) async throws -> Data {
    let (data, _) = try await URLSession.shared.data(from: url)
    return data
}

await 调用

在调用异步函数时,使用 await 关键字。这表示当前点可以挂起,等待异步函数完成。注意,await 并不阻塞线程,而是允许线程去执行其他任务,直到异步操作完成。

Swift 复制代码
swift

Task {
    do {
        let data = try await fetchData(from: someURL)
        print("收到数据: \(data)")
    } catch {
        print("错误: \(error)")
    }
}

2. Task

Task 是并发执行的基本单位,它代表一个异步任务。创建一个 Task 即启动一个并发任务。

创建任务

Swift 复制代码
swift

Task {
    // 异步代码
    let result = await someAsyncFunction()
    print(result)
}

取消任务

任务可以被取消,通过检查 Task.isCancelled 或调用 Task.cancel()

Swift 复制代码
swift

let task = Task {
    // 定期检查是否取消
    while !Task.isCancelled {
        await doSomeWork()
    }
}

// 取消任务
task.cancel()

结构化并发

结构化并发是指任务的生命周期与创建它的作用域绑定,当作用域退出时,所有子任务都会被取消并等待完成。这通过任务组(Task Group)来实现。

Swift 复制代码
swift

func processMultipleRequests() async {
    await withTaskGroup(of: Data.self) { group in
        for url in urls {
            group.addTask {
                return try await fetchData(from: url)
            }
        }
        
        // 收集结果
        for await result in group {
            process(result)
        }
    }
}

3. Actor

Actor 是 Swift Concurrency 中用于保护共享状态的一种类型。它通过隔离(isolation)来确保在同一时刻只有一个任务可以访问其可变状态,从而避免数据竞争(data race)。

定义 Actor

Swift 复制代码
swift

actor BankAccount {
    private var balance: Double
    
    init(initialBalance: Double) {
        balance = initialBalance
    }
    
    func deposit(amount: Double) {
        balance += amount
    }
    
    func withdraw(amount: Double) -> Bool {
        if balance >= amount {
            balance -= amount
            return true
        } else {
            return false
        }
    }
    
    func currentBalance() -> Double {
        return balance
    }
}

使用 Actor

因为 Actor 的方法和属性是隔离的,所以访问它们需要使用 await,表示可能会挂起等待访问权。

Swift 复制代码
swift

let account = BankAccount(initialBalance: 1000)

Task {
    await account.deposit(amount: 200)
    let balance = await account.currentBalance()
    print("当前余额: \(balance)")
}

Actor 的重入(Reentrancy)

Actor 可以处理多个请求,但一次只处理一个。当 Actor 中的一个方法挂起(例如,调用了另一个异步函数)时,Actor 可以处理另一个任务。这可能导致重入,即同一个 Actor 方法在挂起后再次被调用。因此,在设计 Actor 时,需要注意在挂起前保持状态一致,并在恢复后检查状态是否改变。

防止数据竞争

在没有 Actor 的情况下,多个线程同时访问和修改同一个变量会导致数据竞争。例如:

Swift 复制代码
swift

class NonSafeCounter {
    var value = 0
    
    func increment() {
        value += 1
    }
}

如果多个线程同时调用 increment()value 的最终值可能不确定。而使用 Actor 可以安全地管理这种状态。

MainActor

MainActor 是一个特殊的 Actor,它运行在主线程上。任何需要在主线程上执行的操作(如更新 UI)都可以通过标记为 @MainActor 来确保在主线程上运行。

Swift 复制代码
swift

@MainActor
func updateUI() {
    // 更新UI控件
}

Task {
    // 在后台执行异步操作
    let data = await fetchData()
    
    // 回到主线程更新UI
    await updateUI()
}

或者将整个类标记为运行在主线程:

Swift 复制代码
swift

@MainActor
class MyViewController: UIViewController {
    // 所有属性和方法都会在主线程上访问
}

4. 异步序列(AsyncSequence)

异步序列允许我们以异步的方式迭代序列中的元素。

Swift 复制代码
swift

let urls = [URL(string: "https://example.com/1")!, URL(string: "https://example.com/2")!]

for try await data in urls.async.compactMap({ $0 }).map({ try await fetchData(from: $0) }) {
    process(data)
}

5. 异步属性(Async Properties)

除了函数,属性也可以是异步的。

Swift 复制代码
swift

actor SomeActor {
    var someProperty: String {
        get async {
            await someAsyncMethod()
            return "value"
        }
    }
}

总结

Swift Concurrency 提供了一套现代化、安全且高效的并发编程工具。async/await 使得异步代码的编写和阅读更加直观;Task 和结构化并发确保了任务的生命周期管理;Actor 则解决了共享状态下的数据竞争问题。

使用建议:

  • 1、尽量使用 async/await 替代回调(completion handlers)和 Future/Promise。

  • 2、对于需要保护共享状态的场景,使用 Actor。

  • 3、UI 更新务必使用 MainActor 确保在主线程上进行。

  • 4、利用结构化并发来管理多个并发任务,避免任务泄漏。

通过这些工具,Swift 开发者可以编写出更安全、更易维护的并发代码。

Swift Concurrency (async/await) 详解

一、核心概念

1. async/await 基础

Swift 复制代码
swift

// 传统回调方式 → 现代 async/await
func fetchData(completion: @escaping (Result<String, Error>) -> Void) {
    // 回调地狱
}

// 转换为 async/await
func fetchData() async throws -> String {
    // 直接返回结果
}

2. 基本使用

Swift 复制代码
swift

// 1. 声明异步函数
func downloadImage(from url: URL) async throws -> UIImage {
    let (data, _) = try await URLSession.shared.data(from: url)
    guard let image = UIImage(data: data) else {
        throw DownloadError.invalidData
    }
    return image
}

// 2. 调用异步函数
Task {
    do {
        let image = try await downloadImage(from: imageURL)
        // 自动切换到主线程更新UI
        await MainActor.run {
            self.imageView.image = image
        }
    } catch {
        print("下载失败: \(error)")
    }
}

二、Task 系统

1. Task 的类型

Swift 复制代码
swift

// 1. 非结构化任务
func startTasks() {
    // 创建并立即执行
    Task {
        let result1 = await fetchData1()
        print(result1)
    }
    
    // 获取任务引用
    let task = Task {
        try await processData()
    }
    
    // 稍后取消
    task.cancel()
}

// 2. 结构化并发 - withTaskGroup
func downloadAllImages(urls: [URL]) async throws -> [UIImage] {
    try await withThrowingTaskGroup(of: UIImage.self) { group in
        var images: [UIImage] = []
        
        for url in urls {
            group.addTask {
                try await downloadImage(from: url)
            }
        }
        
        // 按完成顺序收集结果
        for try await image in group {
            images.append(image)
        }
        
        return images
    }
}

// 3. 结构化并发 - async let
func fetchUserData() async throws -> (User, [Post], [Photo]) {
    async let user = fetchUser()
    async let posts = fetchPosts()
    async let photos = fetchPhotos()
    
    // 并行执行,等待所有完成
    return try await (user, posts, photos)
}

2. Task 优先级

Swift 复制代码
swift

Task(priority: .userInitiated) {
    // 高优先级任务
}

Task(priority: .background) {
    // 低优先级后台任务
}

Task(priority: .medium) {
    // 默认优先级
}

// 继承优先级
Task.detached {
    // 不继承父任务优先级
}

3. 任务取消

Swift 复制代码
swift

func processLargeFile() async throws {
    // 1. 检查取消状态
    try Task.checkCancellation()
    
    // 2. 定期检查
    for chunk in largeFile.chunks {
        if Task.isCancelled {
            print("任务被取消")
            return
        }
        await process(chunk)
    }
    
    // 3. 使用 withTaskCancellationHandler
    try await withTaskCancellationHandler {
        // 执行任务
        try await uploadFile()
    } onCancel: {
        // 取消时的清理工作
        cleanup()
    }
}

三、Actor 详解

1. Actor 的意义和问题解决

解决的问题:

  • 数据竞争(Data Race):多个线程同时访问/修改同一数据

  • 竞态条件(Race Condition):执行顺序不确定导致结果不一致

传统方案的问题:

Swift 复制代码
swift

class Counter {
    private var value = 0
    private let lock = NSLock()
    
    func increment() {
        lock.lock()
        defer { lock.unlock() }
        value += 1  // 容易忘记加锁,手动管理复杂
    }
}

2. Actor 的核心特性

Swift 复制代码
swift

// 定义 Actor
actor BankAccount {
    private var balance: Double = 0
    private let accountNumber: String
    
    init(accountNumber: String, initialBalance: Double = 0) {
        self.accountNumber = accountNumber
        self.balance = initialBalance
    }
    
    // Actor 方法默认是异步的
    func deposit(amount: Double) {
        balance += amount
        print("存款 \(amount), 余额: \(balance)")
    }
    
    func withdraw(amount: Double) -> Bool {
        guard balance >= amount else {
            print("余额不足")
            return false
        }
        balance -= amount
        print("取款 \(amount), 余额: \(balance)")
        return true
    }
    
    // 同步方法(非隔离)
    nonisolated func getAccountNumber() -> String {
        return accountNumber  // accountNumber 是 let 常量,可以安全访问
    }
    
    // 计算属性
    var currentBalance: Double {
        get async {
            await someAsyncOperation()
            return balance
        }
    }
    
    // Actor 内部可以同步访问自己的状态
    func transfer(to otherAccount: BankAccount, amount: Double) async throws {
        guard self.withdraw(amount: amount) else {
            throw TransferError.insufficientFunds
        }
        await otherAccount.deposit(amount: amount)
    }
}

3. Actor 隔离机制

Swift 复制代码
swift

actor DataManager {
    private var cache: [String: Data] = [:]
    private var accessLog: [String] = []
    
    // 1. 隔离方法 - 只能异步访问
    func getData(for key: String) async -> Data? {
        accessLog.append("读取: \(key)")
        return cache[key]
    }
    
    // 2. nonisolated 方法 - 可以同步访问
    nonisolated func getCacheKeys() -> [String] {
        return Array(cache.keys)  // 错误!不能访问隔离属性
        
        // 正确:返回非隔离数据
        return ["key1", "key2"]
    }
    
    // 3. Actor 内部的同步访问
    func updateCache(key: String, value: Data) {
        // 可以直接访问隔离属性
        cache[key] = value
        accessLog.append("更新: \(key)")
        
        // 也可以调用其他隔离方法
        cleanupOldEntries()
    }
    
    private func cleanupOldEntries() {
        // 私有方法也自动隔离
    }
}

4. MainActor

Swift 复制代码
swift

// 1. 标记整个类在主线程运行
@MainActor
class ViewModel: ObservableObject {
    @Published var data: [String] = []
    
    func loadData() async {
        // 这个方法会自动在主线程执行
        let result = await fetchData()
        data = result  // 更新 @Published 属性会自动在主线程
    }
}

// 2. 标记单个方法
class DataLoader {
    @MainActor func updateUI(with data: Data) {
        // 确保在主线程更新UI
        imageView.image = UIImage(data: data)
    }
    
    func processData() async {
        let data = await fetchData()
        await updateUI(with: data)  // 需要 await
    }
}

// 3. 使用 MainActor.run
Task {
    let data = await fetchData()
    
    await MainActor.run {
        // 这个闭包在主线程执行
        self.label.text = "完成"
        self.imageView.image = UIImage(data: data)
    }
}

5. Sendable 协议

Swift 复制代码
swift

// 1. 可安全跨并发域传递的类型
struct User: Sendable {
    let id: String
    let name: String
    // 所有属性都必须是 Sendable 的
}

// 2. Actor 自动遵循 Sendable
actor Logger: Sendable {
    // Actor 自动是 Sendable
}

// 3. 在异步函数中使用
func processUser(user: User) async {
    // User 是 Sendable,可以安全传递
    await logUser(user: user)
}

// 4. @Sendable 闭包
Task {
    let result = await withCheckedContinuation { continuation in
        someAsyncOperation { value in
            continuation.resume(returning: value)
        }
    }
}

6. Actor 重入问题

Swift 复制代码
swift

actor BankAccount {
    private var balance: Double = 1000
    
    func withdraw(amount: Double) async -> Bool {
        // 第一次检查
        guard balance >= amount else { return false }
        
        // 模拟异步操作(此时可能被其他任务中断)
        await someNetworkCall()
        
        // 重新进入后再次检查
        guard balance >= amount else { return false }
        
        balance -= amount
        return true
    }
}

// 解决方案:使用锁或标记
actor SafeAccount {
    private var balance: Double = 1000
    private var isProcessing = false
    
    func safeWithdraw(amount: Double) async throws -> Bool {
        guard !isProcessing else {
            throw AccountError.operationInProgress
        }
        
        isProcessing = true
        defer { isProcessing = false }
        
        guard balance >= amount else { return false }
        await someNetworkCall()
        guard balance >= amount else { return false }
        
        balance -= amount
        return true
    }
}

四、实际应用模式

1. Repository 模式 + Actor

Swift 复制代码
swift

actor UserRepository {
    private var cache: [String: User] = [:]
    private let apiService: APIService
    
    init(apiService: APIService) {
        self.apiService = apiService
    }
    
    func getUser(by id: String) async throws -> User {
        // 1. 检查缓存
        if let cached = cache[id] {
            return cached
        }
        
        // 2. 网络请求
        let user = try await apiService.fetchUser(id: id)
        
        // 3. 更新缓存
        cache[id] = user
        
        return user
    }
    
    func updateUser(_ user: User) async throws {
        // 1. 更新缓存
        cache[user.id] = user
        
        // 2. 同步到服务器
        try await apiService.updateUser(user)
    }
    
    func clearCache() {
        cache.removeAll()
    }
}

2. ViewModel + @MainActor

Swift 复制代码
swift

@MainActor
class UserViewModel: ObservableObject {
    @Published var users: [User] = []
    @Published var isLoading = false
    @Published var error: Error?
    
    private let repository: UserRepository
    
    init(repository: UserRepository) {
        self.repository = repository
    }
    
    func loadUsers() async {
        isLoading = true
        defer { isLoading = false }
        
        do {
            // 这里在后台线程执行
            let loadedUsers = try await repository.getAllUsers()
            
            // 自动切换到主线程更新 @Published 属性
            self.users = loadedUsers
        } catch {
            self.error = error
        }
    }
    
    func addUser(_ user: User) async {
        do {
            try await repository.addUser(user)
            await loadUsers()  // 刷新列表
        } catch {
            self.error = error
        }
    }
}

3. 并发模式组合

Swift 复制代码
swift

actor ImageCache {
    private var images: [String: UIImage] = [:]
    private let fileManager = FileManager.default
    
    func getImage(for key: String) async -> UIImage? {
        // 1. 内存缓存
        if let cached = images[key] {
            return cached
        }
        
        // 2. 磁盘缓存
        if let diskImage = await loadFromDisk(key: key) {
            images[key] = diskImage
            return diskImage
        }
        
        return nil
    }
    
    func setImage(_ image: UIImage, for key: String) async {
        // 更新内存缓存
        images[key] = image
        
        // 异步保存到磁盘
        Task.detached(priority: .background) {
            await self.saveToDisk(image: image, key: key)
        }
    }
    
    private func saveToDisk(image: UIImage, key: String) async {
        // 磁盘操作
    }
    
    private func loadFromDisk(key: String) async -> UIImage? {
        // 磁盘读取
        return nil
    }
}

五、最佳实践

1. 错误处理

Swift 复制代码
swift

enum DataError: Error {
    case notFound
    case networkError
    case decodingError
}

func fetchData() async throws -> DataModel {
    do {
        return try await apiService.fetch()
    } catch URLError.badURL {
        throw DataError.networkError
    } catch DecodingError.dataCorrupted {
        throw DataError.decodingError
    }
}

// 使用 Task 处理错误
Task {
    do {
        let data = try await fetchData()
        await updateUI(data)
    } catch DataError.networkError {
        await showNetworkError()
    } catch {
        await showGenericError(error)
    }
}

2. 性能优化

Swift 复制代码
swift

// 1. 限制并发数量
func processItems(_ items: [Item]) async {
    await withTaskGroup(of: Void.self) { group in
        let semaphore = AsyncSemaphore(value: 5) // 最多5个并发
        
        for item in items {
            await semaphore.wait()
            group.addTask {
                defer { semaphore.signal() }
                await process(item)
            }
        }
        
        await group.waitForAll()
    }
}

// 2. 使用 continuation 包装回调代码
func fetchWithContinuation() async throws -> Data {
    return try await withCheckedThrowingContinuation { continuation in
        oldAsyncFunction { result in
            switch result {
            case .success(let data):
                continuation.resume(returning: data)
            case .failure(let error):
                continuation.resume(throwing: error)
            }
        }
    }
}

3. 测试

Swift 复制代码
swift

// 1. 测试异步函数
func testAsyncFunction() async throws {
    let result = try await someAsyncFunction()
    XCTAssertEqual(result, expectedValue)
}

// 2. 模拟 actor
actor MockRepository: UserRepositoryProtocol {
    private var users: [User] = []
    
    func addUser(_ user: User) async {
        users.append(user)
    }
    
    func getAllUsers() async -> [User] {
        return users
    }
}

六、注意事项

  1. 避免阻塞调用:不要在 async 函数中使用同步阻塞操作

  2. 合理使用 Task:避免创建过多不必要的 Task

  3. 注意 Actor 重入:在挂起点前后状态可能已改变

  4. 内存管理:注意循环引用,特别是 Task 中捕获 self

  5. 优先级继承:默认情况下,子任务继承父任务优先级

  6. 调试:使用 Instruments 的 Swift Concurrency 模板分析

Swift Concurrency 通过编译器级别的安全检查,大大减少了并发编程中的常见错误,是 Swift 并发编程的未来方向。

相关推荐
寻寻觅觅☆8 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
YJlio8 小时前
1.7 通过 Sysinternals Live 在线运行工具:不下载也能用的“云端工具箱”
c语言·网络·python·数码相机·ios·django·iphone
l1t8 小时前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
赶路人儿9 小时前
Jsoniter(java版本)使用介绍
java·开发语言
ceclar1239 小时前
C++使用format
开发语言·c++·算法
码说AI9 小时前
python快速绘制走势图对比曲线
开发语言·python
Gofarlic_OMS10 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
星空下的月光影子10 小时前
易语言开发从入门到精通:补充篇·网络爬虫与自动化采集分析系统深度实战·HTTP/HTTPS请求·HTML/JSON解析·反爬策略·电商价格监控·新闻资讯采集
开发语言
老约家的可汗10 小时前
初识C++
开发语言·c++
wait_luky10 小时前
python作业3
开发语言·python