大多数浏览器和
Developer App 均支持流媒体播放。
-
利用 Swift 并发消除数据争用
和我们一起探索 Swift 并发的核心概念之一:任务和角色隔离。我们将介绍 Swift 用以消除数据争用的方法,以及它对 App 架构的影响。我们还将讨论代码中的原子性有多么重要,分享 Sendable 检查的细微差别以保持隔离,并且回顾并发系统中的有序性工作假设。
资源
相关视频
WWDC22
WWDC21
-
下载
♪ 柔和乐器演奏的嘻哈音乐 ♪ ♪ 您好 我是 Swift 团队的 Doug 本期视频让我们来聊聊 Swift Concurrency 是如何消除数据竞争的 我们曾介绍过 作为一组语言特性 Swift Concurrency 能够降低编写并发程序的难度 有关这些语言特性的信息 我们建议大家参看 2021 年的 WWDC 相关的课程 本课程对 Swift Concurrency 提供一个不同 但更加全方位的视角 能够帮助您在构建程序时 在不引入数据竞争的情况下 有效使用并发系统 但要做到这一点 我们需要用一个好的类比 因此 我们邀请您一同 徜徉在并发的海洋 在并发之海中 纷繁事项接踵而至 难以预测 但只要有 Swift 保驾护航 您就能从并发之海中 获得惊人成果 让我们一同深入探索吧! 我们从隔离开始 这是 Swift 并发系统的 关键概念之一 能够确保数据相互隔离、互不影响 避免引发数据竞争 让我们从任务隔离开始介绍 在我们的并发之海中 任务就是一艘艘船只 这些船只就是海上的主要工作者 它们都有各自的工作 会从开始到结束按照顺序完成 船只之间是异步的 它们的工作可能会被挂起 只要代码中有 await 操作 最后 任务船只都是独立的 每个任务都有自己的资源 可独立操作 与海中的其他船只互不干扰 只要船只保持完全相互独立 我们的并发就不会产生数据竞争 但无法相互沟通的船只用处也不大 我们来增加些通信交流! 举个例子 一艘船可能有个菠萝 它想与另一艘船分享这个菠萝 于是两艘船在公海碰面 将菠萝从一艘船转移到另一艘船 这就到了现实类比有些 不够用的地方 因为在并发之海中的菠萝 并不是能在两艘船之间转移的实物 它是数据 在 Swift 中有几种方式 可以代表这些数据 该如何定义菠萝的类型呢? 我们喜欢 Swift 中的值类型 所以 我们就把菠萝 看作由重量和成熟度定义的结构体吧 来看看会发生什么 当小船在公海上相遇时 从一艘船到另一艘船 我们传递了菠萝实例的副本 接着 两艘船各自带着一份副本离开 如果您要改变副本 例如通过调用 slice() 和 ripen() 方法 改变一个副本不会 对另一个产生任何影响 Swift 将值类型作为首选 也正是因为如此 所有改变都只有局部影响 该原则有助于值类型保持相互隔离 现在 稍微扩展一下数据模型 往里引入小鸡! 小鸡和菠萝不同 可以说 菠萝只能用来吃 而鸡都是些美丽又有个性的生物 所以 我们来用一个类 来模拟小鸡 就像这样 勇敢的海员要准备交换小鸡了 当两船相遇 就将小鸡分享出去 小鸡属于引用类型 在复制时 不会将小鸡完整复制出去 只会提供对该特定对象的引用 所以 一旦两艘船分道扬镳 就会发现一个问题 两艘船正在进行并发工作 但由于引用了同一个小鸡对象 两艘船彼此不再相互独立 共享的可变数据 很容易引发数据竞争 比如说 如果一艘船在给小鸡喂食 而另一艘船在和小鸡玩耍 就会让小鸡陷入困惑 分享菠萝是安全的 而分享小鸡不是 因此 我们需要某种方法 来确认这一点 于是我们需要在 Swift 编译器中进行检查 来确保小鸡不会被意外 送到另一艘船上 Swift 协议可以有效区分类型 方便您理解各类型行为 Sendable 协议用以描述 可以在隔离域间安全共享的类型 不会引发数据竞争 可以通过编写符合性 使类型成为 Sendable Pineapple 结构体属于值类型 因此符合 Sendable 但是 Chicken 类并不符合 因为小鸡是不同步的引用类型 将 Sendable 建模为协议后 我们就可以描述 要将数据跨隔离域 共享的地方 比如说 当某个任务返回一个值时 就将这个值提供给任何 正在等待该值的任务 比如说这里 我们想要从 Task 中 返回一只 Chicken 结果收到了报错 提醒该操作有风险 因为 Chicken 不符合 Sendable Sendable 约束实际来源于 Task 结构体本身的定义 该定义规定 名为 Success 的 Task 结果类型 必须符合 Sendable 协议 如果您的通用参数将要 跨隔离域传递值 您就应该使用 Sendable 协议进行约束 现在再回头看看船只之间的数据共享 当两艘船在公海相遇 并想要共享数据时 始终需要有人来检查每一项货物 确保货物可以安全共享 这项工作的负责人 就是我们的好朋友 海关检查员 即 Swift 编译器 编译器会确保 只能交换 Sendable 类型 菠萝没问题 可以随意交换 因为它是 Sendable 但是 不能交换小鸡 我们的好朋友海关检查员会拦住我们 避免我们犯错 编译器会在多个环节负责检查 货物是否符合 Sendable Sendable 类型必须有正确构造 而且走私共享数据是不允许的 枚举和结构通常定义值类型 值类型会复制所有实例数据 以产生独立值 因此 只要所有的实例数据可发送 它们同样也符合 Sendable Sendable 可以通过集合以及 其他使用条件符合性的 泛型类型进行传播 一个 Sendable 类型的数组 也符合 Sendable 协议 所以 装满菠萝的 Crate 同样为 Sendable 对于所有这些 Sendable 符合性 Swift 编译器甚至都可以 将其推断为非公有类型 所以 Ripeness、Pineapple 和 Crate 的数据 都属于隐式 Sendable 但假如我们给鸡群建造了一个鸡舍 该类型不能标记为 Sendable 因为它包含非 Sendable 状态 Chicken 非 Sendable 因此小鸡数组 同样非 Sendable 于是编译器会发送一条 错误信息来指出 这种类型不能安全共享 类是引用类型 所以只有 在极个别的情况下 才为 Sendable 例如当 final 类只有不可变存储时 如果我们强行让 Chicken 变为 Sendable 就会导致错误 因为该类包含可变状态 现在 实现引用类型 做到内部同步是可能的 比如说 始终通过一个锁访问 这些类型是概念上的 Sendable 但 Swift 没有办法推理出来 选择 unchecked Sendable 就能 禁用编译器检查 但请务必小心 因为 @unchecked Sendable 使用后 可能出现可变状态走私 会破坏 Swift 的数据竞争安全保证 Task 创建的方式包括 从闭包执行一个新的 独立的任务 就好像 从您的船上送出一艘划艇 当这样做时 我们可以 从原始任务中获取值 并将值传递给新任务 因此就需要检查 Sendable 以便确保不会引入数据竞争 如果我们尝试共享非 Sendable 类型 越过了红线 Swift 编译器会帮我们兜底 给我们发送像这样的错误消息 并不是任务创建时 发生了什么神奇的事 闭包被推断为了 Sendable 闭包 可以用 @Sendable 显式编写 Sendable 闭包是 Sendable 函数类型的值 将 @Sendable 写在 函数类型上可以指明 该函数类型符合 Sendable 协议 也就是说 该函数类型的值 可以传递到其他隔离域并被调用 且不会在其捕获状态上引入数据竞争 通常来说 函数类型不能符合协议 但是 Sendable 协议性质特殊 因为编译器会验证 其语义要求 对 Sendable 类型的元组也有类似的支持 使元组符合 Sendable 协议 允许 Sendable 可被用于 整个语言 我们描述的这个系统 有许多并发执行 且彼此隔离的任务 Sendable 协议描述了可以 在任务间安全共享的类型 Swift 编译器会在各个级别 检查 Sendable 符合性 以保持任务相互隔离 然而 如果绝对禁止 共享任何可变数据 就很难实现任务间的 许多重要协调工作 所以我们需要想办法 一方面实现任务间数据共享 另一方面也避免重新引入数据竞争 这就是 Actor 派上用场的时候了 Actor 提供了一种方法 能够隔离 可被不同任务访问的状态 且该方法可以完成协调 消除数据竞争 Actor 是并发之海中的岛屿 和船只一样 每个岛屿相互独立 有着与海中其他一切 相互隔离的状态 只有当代码在岛上运行时 才能访问该状态 例如 advanceTime 方法 在这个岛屿与世隔绝 该方法驻守在岛上 而且可以进入 岛屿所有的状态 要在岛屿上运行代码 您需要一艘船 船可以抵达岛屿 并在岛上运行代码 此时 该船只可以访问岛屿状态 一次只能有一艘船 访问岛屿并运行代码 才能确保没有发生 对岛屿状态的并发访问 其他船只必须排队等待轮到它们时 才能参观该岛 因为对于船只来说 可能需要等上很长时间 才能参观小岛 进入 Actor 由 “await” 关键字 标记的潜在挂起点 等岛屿空闲后 另一艘船会 再次停泊在挂起点 继续参观 就像两艘船在公海相遇时 船和岛之间的互动需要保持隔离 确保非 Sendable 类型 不会在两者之间传递 假设我们想要将一只鸡从船上 送到岛上的鸡群中 这将在不同的隔离域中创建 对同一个小鸡对象的两个引用 所以 Swift 编译器拒绝了该操作 同样 如果我们想要从岛上 收养一只小鸡当宠物 如果将小鸡带到船上 Sendable 检查会阻止我们 防止会导致数据竞赛的行为 Actor 是引用类型 但与类不同 它们的所有属性和代码都是隔离的 用来防止并发访问 因此 引用来自不同隔离域的 Actor 不会产生问题 这就像是您有了标记好岛屿的地图 您可以跟着地图去参观岛屿 但同时 您仍然需要通过对接程序 才能访问其状态 因此 所有 Actor 类型 都属于隐式 Sendable 您可能想知道 该如何判断 代码是否被隔离到 Actor Actor 隔离取决于上下文 Actor 的实例属性 是隔离在 Actor 的 Actor 上的实例方法 或 Actor 的扩展 默认情况下也是隔离的 比如这个 advanceTime 方法 不是 Sendable 的闭包 例如被传递到 归约算法的闭包 留在 Actor 上 在 actor 隔离上下文中时 是隔离于 actor 的 任务初始化器也从上下文 继承了 Actor 隔离 所以创建的任务将被 安排在启动它的同一 Actor 上 在这情境里 这意味着 我们可以访问鸡群 另一方面 一个分离的任务 不会从其上下文继承 Actor 隔离 因为该任务完全独立于 它创建之处的上下文 可以在这里看到 闭包中的代码 被判定为在 Actor 之外 因为这段代码需要 使用 await 指令 才能引用被隔离的 food 属性 对这种闭包 我们有个术语:非隔离代码 非隔离代码就是 不运行于任何 Actor 的代码 您可以将一个 Actor 中的函数 通过 nonisolated 关键字 显式的变成非隔离的 以此把代码放在 Actor 之外 相同的事也隐含在 用于分离任务的闭包内 也就是说 如果我们想读取一些 隔离于 Actor 的状态 就需要使用 await 指令 来访问该岛并获取所需状态的副本 非隔离异步代码 始终运行在全球合作池 您可以理解为 只有当船离岛航行时 它才会开始运行 所以您必须离开正在访问的岛屿 才能完成工作 这意味着 您会经历一次检查 来确保您没有携带非 Sendable 的数据 看这里 编译器检测到了 潜在的数据竞争 一个非 Sendable 的 Chicken 实例 正试图离开岛屿 再来看看另一个非隔离代码的案例 假设 greet 操作是非隔离的同步代码 该操作不知道什么船只 岛屿 或并发系统 它对此一无所知 我们将其从 Actor 隔离的 greetOne 函数中调用 没关系 不会导致问题 这个同步代码 在岛上被调用时 也会留在岛上 因此它可以自由操作鸡群中的鸡 相反 如果我们有一个 非隔离的异步操作 叫做 greet 那么 greet 会运行在 公海里的一艘船上 多数 Swift 代码都象这样 同步 非隔离于任何 Actor 并且只对给定参数进行操作 所以这类代码会停留在 其被调用的隔离域中 Actor 持有的状态被隔离于 程序其余的部分 一个 Actor 上一次只能运行一个任务 所以对该状态不会发生并发访问 任务进出 Actor 时 都会随时进行 Sendable 检查 来确保没有不同步的可变状态逃脱 综上所述 Actor 因此成为了 Swift 并发程序的构建模块之一 还有一个特殊 Actor 我们常常说到 也就是 Main Actor 可以把 Main Actor 理解为 大海中央的 一座大型岛屿 它代表主线程 所有的绘图和 UI 交互 都发生在这里 所以 如果您想绘制一些东西 就需要在 Main Actor 岛上运行代码 考虑到其对 UI 的重要性 也许我们应该称之为 “UI 大陆” 我们之所以说 Main Actor 是大型岛屿 指的是它包含程序的 很多 UI 相关状态 有很多 UI 框架 和 App 中的代码 都需要在岛上运行 但是说到底 它仍然是 Actor 所以它一次只能运行一个作业 您需要小心避免往 Main Actor 放置太多任务或长期任务 否则会导致 UI 无响应 隔离于 Main Actor 用 MainActor 属性表示 该属性可以应用于函数或闭包 来表示代码必须在 Main Actor 运行 然后 我们称这段代码 隔离于 Main Actor Swift 编译器将保证 隔离于 Main Actor 的代码只会执行在 主线程 借助于 避免其他 Actor 互斥访问相同的机制 如果有人从不隔离于 Main Actor 的 上下文中调用 updateView 就需要引入一个 await 指令 来负责切换到 Main Actor Main Actor 属性 也可以应用于类型 在这种情况下 这些类型的实例 将被隔离于 Main Actor 和之前的其他 Actor 一样 只有在 Main Actor 上时 才能访问属性 并且方法也都隔离于 Main Actor 除非显式选择退出 和普通 Actor 一样 对 Main Actor 类的引用 本身为 Sendable 因为其数据是隔离的 这使得 Main Actor 标注很适合用于 您的 UI 视图和视图控制器 因为它们必须由框架本身 绑定到主线程 您可以与程序中的 其他任务和 Actor 共享对视图控制器的引用 它们可以异步回调视图控制器 来发布结果 这对您的 App 架构有直接影响 在您的 App 中 视图和视图控制器 将位于 Main Actor 其他程序逻辑应该 与该 Main Actor 分离 转而使用其他 Actor 来安全模块化共享状态和任务 以描述独立工作 如果需要 那些任务可以 在 Main Actor 和 其他 Actor 之间穿梭 并发 App 中发生的事情有很多 因此 我们开发了一些好用的工具 来帮助您理解 您可以看看这个讲座 主题为 Swift 并发的可视化与优化 来了解更多信息 让我们更加深入一些 来说说原子性 Swift Concurrency 的目标 是为了消除数据竞争 这也就意味着它消除了 会导致数据损坏的低级数据竞争 您仍然需要在高层次上推断出原子性 我们之前说到过 Actor 一次只能运行一项任务 然而 停止运行 Actor 上的任务后 该 Actor 就可以运行其他任务 这样可以确保程序取得进展 消除死锁的可能性 但是 这需要您考虑把 actor 的不变性 仔细划归于 await 声明内 否则 可能在程序处于意外状态时 导致高级别的数据竞争 哪怕实际上没有数据被破坏 让我们详细讲讲这个例子 假设我们有个 打算往小岛运送菠萝的函数 它位于 Actor 之外 是非隔离的异步代码 也就是说 它在公海中运行 这艘船有了些菠萝和去往岛屿的地图 它要把菠萝存放在那座小岛上 第一个有趣的操作就在这里 即 需要从岛上获取食物阵列的副本 为此 船只需要根据 “await” 关键字发出的信号 来访问该岛 拿到食物副本后 船只驶回公海 继续工作 也就是要往从岛上取来的 两个菠萝参数中 添加菠萝 现在 转到函数的最后一行 现在 船只需要再次访问该岛 将岛上的食物阵列设置为 船上的三个菠萝 请看 一切进展顺利 我们的岛上有了三个菠萝! 但事情发展也可能和这不同 假设当第一艘船正在排队访问该岛时 一艘海盗船溜进来偷走了所有菠萝 现在 我们的船 把三个菠萝存进了小岛 这时候就出现问题了 三个菠萝突然变了 五个菠萝 中间发生了什么? 请注意 对于访问同一 Actor 的状态 这里有两个 await 指令 我们在这里假设 在这两个 await 之间 岛上的食物阵列不会发生改变 但既然这些是 await 指令 那么我们的任务就会在这里挂起 而 Actor 可以做其他 优先级更高的工作 比如说抵御海盗 在这种特定情况下 Swift 编译器将拒绝 彻底修改另一 Actor 的状态 但说真的 我们应该将存放操作 像这样改写为 Actor 上的同步代码 因为同步代码 将不间断地在 Actor 运行 以此 我们可以确定在整个函数中 其他任何人都无法更改岛上的状态 当您在编写 Actor 时 请注意那些可能产生交错的 同步的事务性操作 每个操作在退出时 都应该确保 Actor 状态良好 对于异步 Actor 操作 请不要让事情复杂化 让其主要从同步的 事务性操作中形成 您还需要注意在每个 await 操作中 让 Actor 保持状态良好 这样才能够充分利用 Actor 来消除 低级和高级数据竞赛 在并发程序中 很多事情同时发生 在指令执行的过程中 这些事情发生的顺序可能会有所不同 然而程序通常会依赖以固定的顺序 来处理事件 例如 来自用户输入 或来自服务器消息的事件流 当事件流来到时 它们的影响应该会按顺序发生 Swift Concurrency 提供了用于按序操作的工具 然而 Actor 并不是这样的工具 Actor 会先执行最高优先级的工作 来让整个系统保持响应 这样可以消除优先级倒置 于是 在 Actor 中不会出现 低优先级工作 早于更高优先级工作完成的情况 请注意 与串行调度队列相比 Actor 有着显著差异 前者严格按照先来后到的顺序执行 Swift Concurrency 有很多用于按序工作的工具 第一个工具就是 我们讲了不少的 “Task” Task 按顺序从前往后执行 和您习惯的正常控制流程一致 所以 Task 可以按顺序工作 AsyncStream 可用于 对实际的事件流进行建模 一项任务可以 使用 for-await-in 循环 遍历事件流 依次处理每个事件 AsyncStream 可以被 任意数量的事件产生器共享 产生器可以在向流中添加元素的同时 保持顺序 我们已经说了不少 Swift 的并发模型 在消除数据竞争方面的设计 Swift 运用了隔离的概念 通过 Sendable 检查任务 和 Actor 边界维持隔离状态 然而 我们不可能随时停下手中任务 去标记所有 Sendable 类型 因此 我们需要递进的方式 Swift 5.7 引入了一个构建设置 能够设定 Swift 编译器 检查 Sendability 的严格程度 默认设置为 minimal 这意味着 只有在有人尝试 将某事标记为 Sendable 编译器才会进行诊断 就类似于 Swift 5.5 和 5.6 采取的方案 对于上述情况 不会产生警告或报错 现在 如果您添加 Sendable 符合性 编译器就会发送警告 提醒 Coop 类型 无法是 Sendable 因为 Chicken 不是 Sendable 然而 这个问题以及其他 Sendable 相关的问题 在 Swift 5 中将显示为警告 而不是报错 方便逐个解决问题 为了进一步实现数据竞赛安全 可以启用 targeted 严格并发系统设置 该设置对已经采用了 Swift Concurrency 特性的代码 启用 Sendable 检查 比如 async/await 、Task 或 Actor 该功能可以识别许多操作 例如 在新创建任务中 捕获非 Sendable 类型值的尝试 有时非 Sendable 类型 可能来自另一个模块 也许是一些尚未更新适应 Sendable 协议的软件包 甚至可能是您自己的 还没来得及更新的模块 对于对于来自以上模块的类型 您可以使用 @preconcurrency 属性 暂时禁用 Sendable 警告 属性应用后 针对源文件中 Chicken 类型的 Sendable 警告会被静音 在某些时候 FarmAnimals 模块 会使用 Sendable 符合性进行更新 于是以下两种情况之一将会发生 要么 Chicken 以某种方式变成了 Sendable 在这种情况下 从导入中 删除 preconcurrency 属性即可 要么 Chicken 会被判定为非 Sendable 于是系统会再次给出警告 指出实际上 Chicken 应该是 非 Sendable targeted 的严格设置旨在维持 现有代码的兼容性 和识别潜在数据竞争之间的平衡 不过 如果您想要知道 所有可能发生数据竞争的地方 还有一个选择:complete checking complete checking 近似于 预期的 Swift 6 语义 目的是彻底消除数据竞争 该模式的检查内容完全 覆盖了前两种模式 且会检查模块中的 所有代码 在这里 我们实际上并没有使用 Swift 的并发特性 相反 它只是在调度队列执行工作 且并发执行该代码 调度队列上的异步操作 已知会采用 Sendable 闭包 所以编译器将产生警告提醒 这里存在数据竞争 即当非 Sendable 的 body 被调度队列上运行的代码捕获时 将 body 参数设置为 Sendable 就能够解决这个问题 修改后就消除了警告 现在所有调用 doWork 的地方都知道了 需要提供一个 Sendable 闭包 这样能更方便检查数据竞争 我们可以看到 现在访问函数 就是数据竞争的来源 complete checking 有助于清除 程序中潜在的数据竞争 为了实现 Swift 消除数据竞争的目标 我们最终需要 complete checking 欢迎您一步步实现该目标 采用 Swift 并发模型 安全构建出避免数据竞赛的 App 然后启用严格的并发检查 来消除代码中的错误类别 不必犹豫 您大可用 @preconcurrency 标记导入 来禁用对导入类型的警告 由于这些采用更严格并发检查的模块 编译器会再次检查您的假定 最终 您的代码将获益于 内存安全和数据竞争安全 让您能够专注于构建出色的 App 感谢您陪伴我一同驶过并发之海 ♪
-
-
1:18 - Tasks
Task.detached { let fish = await catchFish() let dinner = await cook(fish) await eat(dinner) }
-
2:31 - What is the pineapple?
enum Ripeness { case hard case perfect case mushy(daysPast: Int) } struct Pineapple { var weight: Double var ripeness: Ripeness mutating func ripen() async { … } mutating func slice() -> Int { … } }
-
3:15 - Adding chickens
final class Chicken { let name: String var currentHunger: HungerLevel func feed() { … } func play() { … } func produce() -> Egg { … } }
-
4:35 - Sendable protocol
protocol Sendable { }
-
4:44 - Use conformance to specify which types are Sendable
struct Pineapple: Sendable { … } //conforms to Sendable because its a value type class Chicken: Sendable { } // cannot conform to Sendable because its an unsynchronized reference type.
-
4:57 - Check Sendable across task boundaries
// will get an error because Chicken is not Sendable let petAdoption = Task { let chickens = await hatchNewFlock() return chickens.randomElement()! } let pet = await petAdoption.value
-
5:26 - The Sendable constraint is from the Task struct
struct Task<Success: Sendable, Failure: Error> { var value: Success { get async throws { … } } }
-
6:23 - Sendable checking for enums and structs
enum Ripeness: Sendable { case hard case perfect case mushy(daysPast: Int) } struct Pineapple: Sendable { var weight: Double var ripeness: Ripeness }
-
6:52 - Sendable checking for enums and structs with collections
//contains an array of Sendable types, therefore is Sendable struct Crate: Sendable { var pineapples: [Pineapple] }
-
7:17 - Sendable checking for enums and structs with non-Sendable collections
//stored property 'flock' of 'Sendable'-conforming struct 'Coop' has non-sendable type '[Chicken]' struct Coop: Sendable { var flock: [Chicken] }
-
7:36 - Sendable checking in classes
//Can be Sendable if a final class has immutable storage final class Chicken: Sendable { let name: String var currentHunger: HungerLevel //'currentHunger' is mutable, therefore Chicken cannot be Sendable }
-
7:58 - Reference types that do their own internal synchronization
//@unchecked can be used, but be careful! class ConcurrentCache<Key: Hashable & Sendable, Value: Sendable>: @unchecked Sendable { var lock: NSLock var storage: [Key: Value] }
-
8:21 - Sendable checking during task creation
let lily = Chicken(name: "Lily") Task.detached {@Sendable in lily.feed() }
-
9:08 - Sendable function types
struct Task<Success: Sendable, Failure: Error> { static func detached( priority: TaskPriority? = nil, operation: @Sendable @escaping () async throws -> Success ) -> Task<Success, Failure> }
-
10:28 - Actors
actor Island { var flock: [Chicken] var food: [Pineapple] func advanceTime() }
-
11:03 - Only one boat can visit an island at a time
func nextRound(islands: [Island]) async { for island in islands { await island.advanceTime() } }
-
11:34 - Non-Sendable data cannot be shared between a task and actor
//Both examples cannot be shared await myIsland.addToFlock(myChicken) myChicken = await myIsland.adoptPet()
-
12:43 - What code is actor-isolated?
actor Island { var flock: [Chicken] var food: [Pineapple] func advanceTime() { let totalSlices = food.indices.reduce(0) { (total, nextIndex) in total + food[nextIndex].slice() } Task { flock.map(Chicken.produce) } Task.detached { let ripePineapples = await food.filter { $0.ripeness == .perfect } print("There are \(ripePineapples.count) ripe pineapples on the island") } } }
-
14:03 - Nonisolated code
extension Island { nonisolated func meetTheFlock() async { let flockNames = await flock.map { $0.name } print("Meet our fabulous flock: \(flockNames)") } }
-
14:48 - Non-isolated synchronous code
func greet(_ friend: Chicken) { } extension Island { func greetOne() { if let friend = flock.randomElement() { greet(friend) } } }
-
15:15 - Non-isolated asynchronous code
func greet(_ friend: Chicken) { } func greetAny(flock: [Chicken]) async { if let friend = flock.randomElement() { greet(friend) } }
-
17:01 - Isolating functions to the main actor
@MainActor func updateView() { … } Task { @MainActor in // … view.selectedChicken = lily } nonisolated func computeAndUpdate() async { computeNewValues() await updateView() }
-
17:38 - @MainActor types
@MainActor class ChickenValley: Sendable { var flock: [Chicken] var food: [Pineapple] func advanceTime() { for chicken in flock { chicken.eat(from: &food) } } }
-
19:58 - Non-transactional code
func deposit(pineapples: [Pineapple], onto island: Island) async { var food = await island.food food += pineapples await island.food = food }
-
20:56 - Pirates!
await island.food.takeAll()
-
21:57 - Modify `deposit` function to be synchronous
extension Island { func deposit(pineapples: [Pineapple]) { var food = self.food food += pineapples self.food = food } }
-
23:56 - AsyncStreams deliver elements in order
for await event in eventStream { await process(event) }
-
25:02 - Minimal strict concurrency checking
import FarmAnimals struct Coop: Sendable { var flock: [Chicken] }
-
25:21 - Targeted strict concurrency checking
@preconcurrency import FarmAnimals func visit(coop: Coop) async { guard let favorite = coop.flock.randomElement() else { return } Task { favorite.play() } }
-
26:53 - Complete strict concurrency checking
import FarmAnimals func doWork(_ body: @Sendable @escaping () -> Void) { DispatchQueue.global().async { body() } } func visit(friend: Chicken) { doWork { friend.play() } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。