ios开发方向——swift并发进阶核心 Task、Actor、await 详解

await 是 Swift 并发中配合 async 函数使用的「轻量暂停器」------ 它会暂停当前 Task 的执行(但不阻塞线程),等异步函数完成后拿到结果再继续往下跑,让异步代码能像同步代码一样线性书写。


一、先搞懂前提:什么是 async 函数?

await 不能单独用,必须配合 async 标记的函数 / 方法 (异步函数)。async 的作用是告诉编译器:「这个函数可能会耗时,执行时会让出线程,等结果回来再继续」。

Swift 复制代码
// 定义一个异步函数:模拟网络请求
func fetchUserInfo() async throws -> String {
    // 模拟耗时操作(比如网络请求、数据库查询)
    await Task.sleep(2 * 1_000_000_000) // 暂停2秒(注意这里也用了await)
    return "用户信息:张三"
}

二、await 的 3 个核心作用

1. 「暂停」当前 Task,不阻塞线程

这是 await 最关键的特性:

  • 当执行到 await 时,当前 Task 会主动让出执行权,线程可以去执行其他 Task;
  • 等异步函数执行完,系统会自动把当前 Task「唤醒」,从 await 之后的代码继续执行。

💡 通俗类比:把 Task 比作「外卖员」,线程比作「电动车」。外卖员(Task)到了餐厅(执行 await),不会一直占着电动车(不阻塞线程),而是下车等餐(暂停 Task);电动车可以去送其他外卖(执行其他 Task);等餐做好了(异步函数完成),外卖员再骑上原来的电动车(或其他空闲电动车)继续送这单(从 await 后继续执行)。


2. 「等待」异步结果,线性获取返回值

await 会等异步函数执行完,直接拿到返回值(或错误),避免了传统的「回调地狱」。

对比 1:传统回调写法(恶心的嵌套)
Swift 复制代码
// 传统回调写法:嵌套多层,难以维护
func loadDataWithCallback() {
    fetchUserID { userID in
        fetchUserName(by: userID) { userName in
            fetchUserAvatar(by: userName) { avatar in
                // 终于拿到所有数据,更新UI
                DispatchQueue.main.async {
                    self.updateUI(name: userName, avatar: avatar)
                }
            }
        }
    }
}
对比 2:async/await 写法(清爽线性)
Swift 复制代码
// async/await 写法:像同步代码一样从上到下写
func loadDataWithAwait() async {
    do {
        // 1. 等待获取用户ID
        let userID = try await fetchUserID()
        // 2. 等待获取用户名(依赖上一步的userID)
        let userName = try await fetchUserName(by: userID)
        // 3. 等待获取头像(依赖上一步的userName)
        let avatar = try await fetchUserAvatar(by: userName)
        // 4. 切回主线程更新UI
        await MainActor.run {
            self.updateUI(name: userName, avatar: avatar)
        }
    } catch {
        print("加载失败:\(error)")
    }
}

3. 「桥接」Actor 隔离域

之前讲过 Actor:Actor 内部的隔离方法 / 属性,外部不能直接访问,必须用 await 调用。这里的 await 就是在「桥接」两个 Actor 的隔离域,让系统调度目标 Actor 执行代码,执行完再返回结果。

Swift 复制代码
// 定义一个 Actor
actor BankAccount {
    private var balance: Double = 1000
    
    func getBalance() -> Double {
        balance
    }
}

// 外部调用 Actor 的方法,必须用 await
let account = BankAccount()
Task {
    // await 桥接隔离域:等待 BankAccount 调度执行 getBalance()
    let balance = await account.getBalance()
    print("余额:\(balance)")
}

三、await 的 2 个关键注意点

1. 只能在「异步上下文」中使用

await 不能随便写在普通同步函数里,必须在以下两种异步上下文中:

  • Task 闭包Task { ... } 里的代码是异步上下文;
  • async 函数 / 方法 :被 async 标记的函数里的代码是异步上下文。
Swift 复制代码
// ❌ 错误:普通同步函数里不能用 await
func wrongFunction() {
    let result = try await fetchUserInfo() // 编译报错!
}

// ✅ 正确1:在 Task 里用 await
func rightFunction1() {
    Task {
        let result = try await fetchUserInfo()
        print(result)
    }
}

// ✅ 正确2:在 async 函数里用 await
func rightFunction2() async {
    let result = try await fetchUserInfo()
    print(result)
}

2. 不保证「执行顺序」,只保证「依赖顺序」

await 只保证代码的依赖顺序 (比如先拿 userID 再拿 userName),但不保证系统的执行顺序(比如中间可能穿插执行其他 Task)。

不过这对开发者来说是透明的,你只需要按逻辑顺序写 await 就行,系统会自动处理调度。


四、快速总结

概念 作用
async 标记函数为「异步函数」,表示可能会耗时、会让出线程
await 配合 async 函数使用:1. 暂停当前 Task(不阻塞线程)2. 等待异步函数完成,拿到结果3. 桥接 Actor 隔离域

Swift 中的 Task 与 Actor 详解

Task 和 Actor 是 Swift 5.5+ 引入的Swift Concurrency 原生并发体系 的两大核心基石。其中,Task 是异步代码的执行载体 ,负责管理异步任务的生命周期、优先级、取消和错误处理;Actor 是并发安全的状态封装单元,从语言层面彻底解决多线程共享可变状态的「数据竞争」问题,二者配合构成了 Swift 原生、安全、高效的并发编程模型。


一、Task:异步任务的执行单元

1. 核心定位

Task 是 Swift 并发中最小的异步执行单元 ,所有 async/await 标记的异步代码,都必须运行在 Task 的上下文中。它是对协程(Coroutine)的高层封装,替代了 GCD 的 DispatchWorkItem、Operation 等传统方案,让异步代码可以像同步代码一样线性书写,彻底摆脱回调嵌套的「回调地狱」。

一句话本质

Task = Swift 结构化并发里的「独立任务单元」,像一个有生命周期、能取消、能返回结果的 "异步工作包"。

它不是 GCD 的简单替代品,而是一套全新的、更安全的任务管理体系。

想象你是项目主管

  • 你手里有一堆活(主线程代码)
  • 你可以把「查资料」「做图表」这种耗时活,分配给下属去做
  • 这个「下属 + 他的任务」,就是一个 Task
  • 你可以:
    • 等他做完拿结果(await
    • 中途让他别做了(cancel
    • 给他标「紧急」优先级(priority
    • 他如果是你团队的,会继承你的工作节奏(普通 Task
    • 他如果是外包的,完全独立(detached Task

核心 4 个本质(必须记住)

1. Task 是「结构化并发」的基本单位

和 GCD 的 async 完全不同:

  • GCD 的闭包是逃逸的:丢出去就不管了,生命周期失控
  • Task 是结构化的:默认受父 Task 管控,父 Task 取消,子 Task 也会自动取消

2. 两种 Task:普通 vs Detached

类型 继承上下文 适用场景
普通 Task { ... } ✅ 继承优先级、取消状态、Actor 隔离域 绝大多数场景
Task.detached { ... } ❌ 完全独立 极少数需要完全脱离上下文的场景

3. Task 支持 3 个核心能力

  • 能取消 :调用 task.cancel(),内部用 Task.isCancelled 检查响应
  • 能返回结果Task<Success, Failure>,用 task.value 获取结果
  • 能等待 :内部可以写 await,等待其他异步操作

4. Task 不一定在后台线程

取决于它的隔离域

  • @MainActor 里创建的 Task → 主线程执行
  • 在普通 Actor 里创建的 Task → 该 Actor 的串行执行器
  • 无隔离域的 Task → 系统分配的后台线程

最经典的执行顺序(必看)

Swift 复制代码
print("1")
Task {
    print("2")
    // 模拟耗时1秒
    try await Task.sleep(nanoseconds: 1_000_000_000)
    print("3")
}
print("4")

输出顺序:

复制代码
1
4
2
(1秒后)3

原因:

  • Task 是异步启动,不阻塞当前线程
  • 先执行完当前同步代码(1→4)
  • 再执行 Task 里的代码(2→等待→3)

常见误区避坑

  1. 误区 :Task 就是 GCD 的 async 替代品纠正:不是,Task 有取消、优先级继承、结构化生命周期,比 GCD 安全得多。

  2. 误区 :Task 一定在后台线程纠正:不一定,@MainActor 里的 Task 就在主线程。

  3. 误区 :Task 取消后会立刻停止纠正:不会,Task 取消是「协作式」的,必须内部检查 Task.isCancelled 并主动停止。

2. 核心特性

  • 结构化并发(默认行为):默认创建的 Task 有明确的父子层级关系,父 Task 会自动等待所有子 Task 完成,子 Task 的错误会向上冒泡给父 Task,生命周期自动管理,避免「野任务」泄漏。
  • 协作式取消机制 :Task 内置取消能力,取消信号会沿着结构化层级自动向下传播给所有子 Task;取消不是强制终止,而是通过 isCancelledcheckCancellation() 让任务在合适时机优雅退出,避免资源泄漏。
  • 上下文自动继承 :默认 Task 会自动继承当前上下文的优先级、Actor 隔离域、任务本地值、取消状态。比如在 @MainActor 里创建的 Task,默认运行在主线程。
  • 优先级调度 :支持 userInitiatedutilitybackground 等系统优先级,系统会根据优先级调度 CPU 资源,高优先级任务优先执行。
  • 原生错误处理 :完全兼容 Swift 的 throw/do-catch 体系,可通过 Task.result 获取成功值或错误。

3. 核心用法与分类

(1)基础 Task 创建

最常用的场景,继承当前上下文,结构化绑定:

Swift 复制代码
// 基础异步 Task
Task {
    // 执行 async/await 异步代码
    let (data, _) = try await URLSession.shared.data(from: apiURL)
    let model = try JSONDecoder().decode(UserModel.self, from: data)
    await updateUI(with: model)
}

// 带错误处理的完整写法
Task {
    do {
        let (data, _) = try await URLSession.shared.data(from: apiURL)
        let model = try JSONDecoder().decode(UserModel.self, from: data)
        await updateUI(with: model)
    } catch {
        print("请求失败: \(error.localizedDescription)")
    }
}

// 获取 Task 执行结果
let dataTask = Task {
    try await fetchData()
}
// await 等待任务完成,获取结果
let result = try await dataTask.value
(2)TaskGroup:并行执行多个子 Task

用于管理一组动态创建的子 Task,实现真正的并行执行,父 Task 会等待所有子 Task 完成,结构化管理生命周期,适合批量并发请求:

Swift 复制代码
func fetchBatchData() async throws -> [Data] {
    try await withThrowingTaskGroup(of: Data.self) { group in
        // 向组内添加多个并行子 Task
        for url in apiURLList {
            group.addTask {
                try await URLSession.shared.data(from: url).0
            }
        }
        // 收集所有子 Task 的结果
        var results: [Data] = []
        for try await data in group {
            results.append(data)
        }
        return results
    }
}
(3)Detached Task:非结构化任务

脱离当前上下文的独立 Task,不继承父 Task 的优先级、Actor 隔离域、取消状态,生命周期完全独立,和 GCD 全局队列执行类似,非必要不使用,极易造成任务泄漏和上下文混乱:

Swift 复制代码
Task.detached(priority: .background) {
    // 独立执行的后台任务,不继承当前上下文
    let largeFileData = await downloadLargeFile()
    await saveToDisk(data: largeFileData)
}

4. 核心取消 API

  • Task.isCancelled:判断当前 Task 是否已被取消
  • Task.checkCancellation():如果 Task 已取消,直接抛出 CancellationError 中断执行
  • task.cancel():手动取消一个 Task,取消信号会传播给所有结构化子 Task
  • Task.yield():主动让出当前执行权,让调度器优先执行其他高优先级任务

二、Actor:并发安全的状态封装单元

一句话本质

Actor = Swift 里的「数据安全隔离单元」,像一个有 "专属门禁" 的房间,只有房间里的人能直接碰里面的东西,外面的人必须排队请求。

它是 Swift 解决多线程数据竞争的终极方案,比手动加锁安全 100 倍。


生活例子秒懂

想象银行的单人柜台

  • 每个柜台有自己的钱箱(Actor 的内部状态)
  • 只有柜员(Actor 内部的方法 / 属性)能直接碰钱箱
  • 外面的客户 (其他代码)想存钱 / 取钱,必须叫号排队 (通过 await 调用 Actor 的方法)
  • 同一时间只有一个客户能办理业务(串行执行消息

这就彻底解决了:两个人同时取钱,钱箱余额算错的问题。


核心 4 个本质(必须记住)

1. Actor 是「引用类型」,但比 class 多了数据隔离

  • 和 class 一样,用 let 声明是引用
  • 但比 class 多了编译器强制的状态隔离
    • 内部的 var 属性,外部绝对不能直接访问
    • 只能通过 Actor 的方法间接操作

2. 外部调用必须用 await

因为 Actor 是串行处理消息的:

  • 外部调用它的方法,必须等它处理完上一个请求
  • 所以必须用 await 挂起当前任务,排队等待

3. 内部方法是「同步」的,但外部看起来是「异步」的

  • Actor 内部的方法执行时,不会被其他任务打断(串行)
  • 所以方法本身可以写同步代码
  • 但外部调用必须 await,因为要排队

4. let 常量可以直接访问,不用 await

  • let 是线程安全的(不会变)
  • 所以外部可以直接读,不用排队

最直观的代码示例

定义 Actor

cpp 复制代码
// 银行账户 Actor
actor BankAccount {
    // 内部状态:余额,外部不能直接碰
    private var balance: Int = 0
    
    // 存钱:内部方法,直接操作 balance
    func deposit(_ amount: Int) {
        balance += amount
        print("存了 \(amount),余额:\(balance)")
    }
    
    // 取钱:内部方法,直接操作 balance
    func withdraw(_ amount: Int) -> Bool {
        guard balance >= amount else {
            print("余额不足")
            return false
        }
        balance -= amount
        print("取了 \(amount),余额:\(balance)")
        return true
    }
}

使用 Actor

cpp 复制代码
let account = BankAccount()

// 同时开两个 Task 操作账户
Task {
    await account.deposit(100)
    await account.withdraw(50)
}

Task {
    await account.deposit(200)
    await account.withdraw(150)
}

输出顺序(永远串行,不会乱):

复制代码
存了 100,余额:100
取了 50,余额:50
存了 200,余额:250
取了 150,余额:100

如果是普通 class,不加锁的话,余额肯定会算错!


常见误区避坑

  1. 误区 :Actor 就是加了锁的 class纠正:不是,Actor 是编译器级别的隔离,比手动加锁更安全、更高效,而且不会死锁(因为串行)。

  2. 误区 :Actor 内部的方法都是异步的纠正:不是,方法本身是同步的,只是外部调用必须 await 排队。

  3. 误区 :所有属性都不能外部访问纠正:let 常量可以直接访问,不用 await

1. 核心定位与解决的痛点

传统多线程编程的最大痛点,就是共享可变状态导致的数据竞争(Data Race):多个线程同时读写同一块内存,会导致数据错乱、崩溃、难以复现的 bug。传统方案(锁、串行队列)需要开发者手动保证线程安全,极易出错且维护成本极高。

Actor 是 Swift 从语言层面提供的终极解决方案,它是一种引用类型 ,通过Actor 隔离域(Actor Isolation) 从编译器和运行时双重保证:同一时间,只有一个任务可以访问 Actor 内部的可变状态和隔离方法,彻底杜绝数据竞争,无需开发者手动加锁。

2. 核心特性

  • 隔离域机制 :每个 Actor 都有独立的隔离域,所有存储属性、实例方法默认都在隔离域内,只能在 Actor 内部直接访问;外部必须通过 await 异步调用,编译器会强制校验,禁止直接跨 Actor 访问可变状态。
  • 串行执行保障:Actor 隔离域内的代码,同一时间只会有一个任务执行,天然保证线程安全,无需额外的锁机制。
  • 可重入性 :Actor 的方法是可重入的 ------ 当方法内执行 await 暂停时,Actor 会释放执行权,允许其他任务进入执行。这是 Actor 避免死锁的核心设计,也是最容易踩坑的点。
  • Sendable 协议约束 :跨 Actor 传递的数据,必须遵守 Sendable 协议(标记类型是线程安全的,如值类型、不可变引用类型等),Swift 6 中该检查默认强制开启,编译期就杜绝不安全的数据传递。
  • 全局 Actor 支持 :Swift 提供了预定义的全局 Actor MainActor,绑定主线程,所有标记 @MainActor 的属性、方法、类型,都必须运行在主线程,完美解决 UI 更新的线程安全问题。同时支持自定义全局 Actor。

3. 核心用法

(1)基础 Actor 定义与使用
Swift 复制代码
// 定义一个 Actor,管理银行账户的共享状态
actor BankAccount {
    // 可变状态,被 Actor 隔离,外部无法直接访问
    private var balance: Double
    
    init(initialBalance: Double) {
        self.balance = initialBalance
    }
    
    // 隔离方法,外部必须 await 调用
    func deposit(amount: Double) {
        balance += amount
    }
    
    func withdraw(amount: Double) -> Bool {
        guard balance >= amount else { return false }
        balance -= amount
        return true
    }
    
    func getCurrentBalance() -> Double {
        balance
    }
}

// 外部使用 Actor
let account = BankAccount(initialBalance: 1000)

// 跨 Actor 调用,必须使用 await,等待 Actor 调度执行
Task {
    await account.deposit(amount: 500)
    let isSuccess = await account.withdraw(amount: 800)
    let balance = await account.getCurrentBalance()
    print("当前余额: \(balance)") // 输出 700.0
}
(2)nonisolated 非隔离成员

如果 Actor 的成员不涉及可变状态,是线程安全的,可以用 nonisolated 标记,脱离 Actor 隔离域,外部可以直接同步调用,无需 await:

Swift 复制代码
actor BankAccount {
    // 不可变属性,可标记为 nonisolated
    nonisolated let accountNumber: String
    private var balance: Double
    
    init(accountNumber: String, initialBalance: Double) {
        self.accountNumber = accountNumber
        self.balance = initialBalance
    }
    
    // 非隔离方法,不可访问隔离的可变状态,外部可直接同步调用
    nonisolated func getAccountInfo() -> String {
        "账号: \(accountNumber)"
    }
}

let account = BankAccount(accountNumber: "6222xxxx", initialBalance: 1000)
// 直接同步调用,无需 await
print(account.getAccountInfo())
(3)MainActor 全局 Actor

最常用的全局 Actor,绑定主线程,专门用于 UI 更新,是 iOS 开发中最核心的 Actor 应用:

Swift 复制代码
import SwiftUI

// 标记整个 ViewModel 运行在主线程
@MainActor
class UserViewModel: ObservableObject {
    @Published var userList: [User] = []
    @Published var isLoading: Bool = false
    
    func loadUsers() async {
        isLoading = true
        defer { isLoading = false }
        
        // 耗时网络请求,后台执行,await 等待结果
        do {
            let users = try await UserService.shared.fetchUsers()
            // 直接赋值,无需手动切线程,因为整个 ViewModel 在 @MainActor 隔离域
            self.userList = users
        } catch {
            print("加载失败: \(error)")
        }
    }
}

// 单独标记方法/属性,切回主线程
func updateUserUI() async {
    let user = await fetchCurrentUser()
    // 切回主线程更新 UI
    await MainActor.run {
        self.nameLabel.text = user.name
        self.avatarImageView.image = user.avatar
    }
}
(4)自定义全局 Actor

通过 @globalActor 自定义全局 Actor,实现全局统一的隔离域,比如专门用于数据库操作的 Actor:

Swift 复制代码
// 自定义全局 Actor,用于数据库操作
@globalActor
enum DatabaseActor {
    static let shared = DatabaseManager()
}

actor DatabaseManager {
    // 数据库核心操作实现
}

// 标记类型/方法使用该全局 Actor
@DatabaseActor
func saveUserToDatabase(_ user: User) throws {
    // 运行在 DatabaseActor 的隔离域,保证数据库操作的线程安全
}

4. 核心注意点:可重入性的坑

Actor 的可重入性是最容易被误解、最容易踩坑的点。很多开发者误以为 Actor 和串行队列一样,方法会完整串行执行,但实际上,当方法内遇到 await 暂停时,Actor 会释放执行权,允许其他任务进入修改状态,导致恢复执行时,状态和暂停前不一致。

典型错误示例:

Swift 复制代码
actor BankAccount {
    var balance: Double = 0
    
    func withdrawWithRetry(_ amount: Double) async {
        // 第一次校验余额
        guard balance >= amount else {
            print("余额不足,等待充值...")
            // await 暂停,Actor 释放执行权,其他任务可以修改 balance
            await Task.sleep(3 * 1_000_000_000) // 等待3秒
            // 恢复执行时,balance 可能已经被其他任务修改!
            guard balance >= amount else {
                print("仍然余额不足,取款失败")
                return
            }
        }
        balance -= amount
        print("取款成功")
    }
    
    func deposit(amount: Double) {
        balance += amount
    }
}

解决方案:

  • 避免在 Actor 的方法中,await 前后依赖同一个可变状态
  • 提前把需要的状态捕获到局部常量中,await 后使用局部常量,而非重新读取 Actor 的属性
  • 尽量让 Actor 的方法保持原子性,减少不必要的 await 调用

三、Task 与 Actor 的协同关系

Task 和 Actor 是 Swift 并发体系的一体两面,二者深度配合,共同构成完整的并发编程模型:

  1. Task 的 Actor 上下文自动继承 在 Actor 隔离域内创建的 Task,会自动继承该 Actor 的隔离域,Task 内的代码默认运行在该 Actor 的隔离域内,无需额外的 await 即可直接访问 Actor 的隔离成员:
Swift 复制代码
actor DataCacheManager {
    var cache: [String: Data] = [:]
    
    func prefetchData(for key: String) {
        // 在 Actor 内创建 Task,自动继承当前 Actor 的隔离域
        Task {
            // 可以直接访问 cache,无需 await
            let data = await NetworkService.shared.fetchData(key: key)
            cache[key] = data
        }
    }
}
  1. Task 与 MainActor 的无缝配合日常开发中最常见的场景:后台 Task 执行耗时操作,完成后切回 MainActor 更新 UI,Task 的上下文继承完美适配这个场景,写法极其简洁:
Swift 复制代码
// 非主线程上下文创建 Task
Task {
    // 后台执行耗时网络请求
    let (data, _) = try await URLSession.shared.data(from: apiURL)
    let model = try JSONDecoder().decode(Model.self, from: data)
    // 直接标记闭包运行在 MainActor,整个闭包都在主线程执行
    await MainActor.run {
        self.tableView.reloadData()
        self.titleLabel.text = model.title
    }
}

// 更简洁的写法:直接给 Task 标记 @MainActor
Task { @MainActor in
    let model = try await fetchModel()
    // 整个闭包都运行在主线程,直接更新 UI
    self.titleLabel.text = model.title
}
  1. 跨 Actor 的 Task 调度 当在一个 Actor 中调用另一个 Actor 的方法时,await 会让当前 Task 暂停,等待目标 Actor 调度执行完成后,再恢复当前 Task 的执行,整个线程切换、调度过程由 Swift 运行时自动管理,无需开发者手动干预。

四、最佳实践与常见避坑指南

1. Task 最佳实践

  • 优先使用结构化 Task,避免滥用 Detached Task,防止任务泄漏和上下文混乱
  • 长耗时任务必须处理取消逻辑,在 await 前后检查取消状态,及时释放无用资源
  • 避免创建无限制的并行 Task,使用 TaskGroup 控制并发数量,防止系统资源耗尽
  • Task 闭包中避免强持有 self,使用 [weak self] 避免循环引用
  • 合理设置 Task 优先级,避免高优先级任务抢占过多系统资源

2. Actor 最佳实践

  • 只把需要线程安全的共享可变状态封装到 Actor 中,不要过度使用 Actor,避免不必要的异步调度开销
  • 牢记 Actor 的可重入性,永远不要假设 await 前后 Actor 的状态保持不变
  • 合理使用 nonisolated 标记不可变、无状态的成员,减少不必要的异步调用
  • 严格遵守 Sendable 协议约束,不要滥用 @unchecked Sendable 绕过编译器检查
  • 优先使用系统预定义的 MainActor 处理 UI 相关逻辑,不要自己实现主线程调度
  • Actor 适合保护同步状态变更,不适合做异步流程控制,不要把复杂的异步流程都塞到 Actor 里

3. 常见高频坑

  • Task 循环引用:Task 闭包强持有 self,self 又强持有 Task 实例,导致内存泄漏
  • Actor 可重入性导致的状态错乱:await 前后读取的属性值被其他任务修改,逻辑不符合预期
  • 忽略 Task 取消:长耗时任务没有处理取消,导致无用的网络请求、磁盘 IO 持续占用资源
  • 滥用 @MainActor:把非 UI 相关的耗时操作放到 MainActor 中执行,导致主线程卡顿、UI 掉帧
  • 跨 Actor 直接访问属性:编译器报错,必须通过 await 调用方法,或者把属性设为 nonisolated 不可变
  • 误以为 Actor 是串行队列:Actor 不保证调用的执行顺序,入队顺序≠执行顺序,不要用 Actor 做严格的串行流程控制
相关推荐
小辉同志2 小时前
208. 实现 Trie (前缀树)
开发语言·c++·leetcode·图论
A-刘晨阳2 小时前
当数据学会“秒回“:工业4.0时代的实时计算革命
开发语言·数据库·perl
沐知全栈开发2 小时前
Lua 基本语法
开发语言
EnglishJun2 小时前
ARM嵌入式学习(十九)--- 字符设备驱动的注册与调用流程
arm开发·学习
小李子呢02112 小时前
前端八股JS---ES6新增内容
开发语言·javascript·ecmascript
yaoxin5211232 小时前
381. Java IO API - 控制文件树遍历流程
java·开发语言
zhaoshuzhaoshu2 小时前
Python 语法之控制结构详解
开发语言·python
咚为2 小时前
深入理解 Rust 的静态分发与动态分发:从 `impl Trait` 到 `dyn Trait`
开发语言·后端·rust
Engineer邓祥浩2 小时前
JVM学习笔记(8) 第三部分 虚拟机执行子系统 第7章 虚拟机类加载机制
jvm·笔记·学习