Swift 并发实战:从 Task 到 Actor 重入性,我掉进了“逻辑不一致”的坑

一、 前言

Swift 5.5 引入的结构化并发(Structured Concurrency)让我们告别了 Completion Handler 的地狱。但你以为用了 TaskActor 就万事大吉了吗?最近在处理一个"账户存款"的 Demo 时,我发现即便代码在编译器看来是"内存安全"的,业务逻辑却依然会崩塌。


二、 概念对撞:Task vs MainActor

  • Task: 异步任务的最小单位。它就像一个轻量级线程,可以挂起和恢复。
  • MainActor : 一个全局独占的执行器,确保代码在主线程运行。它是 DispatchQueue.main.async 的现代声明式替代品。

三、 踩坑演进:一个存款功能的"翻车"现场

第一阶段:初生牛犊(直接加锁思维)

我们想实现一个余额上限为 100 的存款功能。最初的代码长这样:

swift 复制代码
actor BankAccount {
    private var balance: Double = 0
    
    func deposit(amount: Double) async {
        guard balance < 100 else { return } // 初始检查
        
        let rate = await fetchExchangeRate() // 模拟联网挂起
        
        balance += amount * rate
        print("✅ 存款完成,余额: \(balance)")
    }
}

运行结果: 当并发 5 个存款任务时,最终余额竟然变成了 150.0原因分析 :这是典型的 Actor Reentrancy(重入性) 。当第一个任务在 await 处挂起时,锁被释放,后续 4 个任务全部通过了 guard balance < 100 的检查。


第二阶段:进阶防御(双重检查锁)

意识到重入性后,我在 await 之后加了二次检查:

swift 复制代码
func deposit(amount: Double) async {
    guard balance < 100 else { return } 
    
    let rate = await fetchExchangeRate() // 挂起点
    
    // 💡 二次检查:防止挂起期间被别人偷跑
    guard balance < 100 else { 
        print("🚨 余额已达上限,取消存款")
        return 
    }
    balance += amount * rate
}

运行结果: 虽然拦截了一部分,但最终余额依然可能出现 130.0原因分析balance < 100 只能保证当前没满,但不能保证"加完之后"不满。如果当前余额 90,任务 A 存 40,检查通过,结果就变成了 130。内存安全不等于业务原子性。


第三阶段:终极方案(预判式原子检查)

要实现绝对的业务一致性,必须在加法前进行"结果预判":

swift 复制代码
func deposit(amount: Double) async {
    let rate = await fetchExchangeRate()
    
    // 💡 架构师思维:计算预期结果并校验
    let projectedBalance = balance + (amount * rate)
    guard projectedBalance <= 100 else {
        print("🚨 严格拦截:存入后将达 \(projectedBalance),已超限!")
        return
    }
    
    balance = projectedBalance
    print("✅ 存款成功,最终余额: \(balance)")
}

四、 深度总结:避坑指南

  1. Actor 不是万能锁 :它只保证同一时间只有一个任务修改变量(内存安全),不保证跨越 await 的逻辑连贯性(逻辑原子性)。
  2. await 之后是"新世界" :永远记住,await 回来后,self 的所有状态都可能已经被改过了。
  3. 不要在 await 之前做关键决策 :如果业务逻辑依赖于状态,务必在 await 恢复后的第一行重新获取快照或校验。
  4. MainActor 的边界 :在 Task 中调用 MainActor 方法必须 await,这是编译器在帮你强制执行线程切换。

五、 结语

Swift 并发模型将很多运行时崩溃变成了编译期错误,这是巨大的进步。但作为开发者,我们不能丧失对并发逻辑的敬畏。Actor 重入性是每一位 iOS 工程师进阶路上的必修课。


相关推荐
大熊猫侯佩3 天前
Xcode 预览新升级:arguments 让界面调试井井有条
swiftui·xcode·swift
大熊猫侯佩4 天前
WWDC26:把 SwiftUI Toolbar 真正握在手心里
swiftui·swift·apple
大熊猫侯佩5 天前
WWDC 26 全新 ResultsObserver:终于能在 View 外面盯着 SwiftData 了
swiftui·swift·apple
大熊猫侯佩5 天前
别再把大模型供在云端了,WWDC26 CoreAI 大模型下凡实战
ai编程·swift·wwdc
00后程序员张5 天前
有没有更轻量的 iOS 开发环境?快蝎kxapp轻量化路径与构成
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
sakiko_6 天前
Swift学习笔记39-实战注意事项
前端·笔记·学习·swift
东坡肘子6 天前
Apple Intelligence 已通过审核,即将在中国提供服务 -- 肘子的 Swift 周报 #148
人工智能·swiftui·swift
初级代码游戏7 天前
iOS开发 Swift 速记7:结构体和类
开发语言·ios·swift
初级代码游戏9 天前
iOS开发 Swift 速记5:高级运算符
ios·移动开发·swift
初级代码游戏9 天前
iOS开发 Swift 速记4:函数与闭包
开发语言·ios·swift