探索 Swift Concurrency (1)
Swift 并发是 WWDC 21 上的一个重磅改进,提供了 async/await
的语法,
让代码结构更加流程化,更加便于理解和调试。
官方指南:开始使用 Swift 并发
actor
WWDC Sessions:
actor 和 class 很相似,都是 Ref Type。
但是 Actor 保证了内部数据的唯一性,即一次只有一个函数能调用,
如果有多个函数同时调用该属性,则需要排队。
保证了在并发环境下的数据竞争 (Data Races)。
其用法与 struct/class 一致。
Main Actor
WWDC Session:利用 Swift Actor 保护可变状态
简单理解就是:主线程。
所有的 UI Updates 都需要在 Main Actor 环境下完成。
有以下几种写法:
1 | Task { in |
1 | func updateUI() { |
1 | Task { |
1 |
|
async let
WWDC Session:探索 Swift 中的结构化并发
async let
可以让多个操作一起执行,到真正需要使用的时候再 await
。
1 | func fetchImage() async { |
这个例子中,如果只用 await
,image 和 metaData 会依次获取,在获取期间程序挂起 (Suspended),不会继续向下执行。
如果 image 的获取比较慢,那么就会卡在这里。
1 | func fetchImage() async { } |
Task Group
WWDC Session:探索 Swift 中的结构化并发
多个任务并发执行的时候可以使用 Task Group
1 | // Throwing Task Group |
TaskGroup
常用在一组数据的处理,例如:获取所有商品的缩略图…
由于有很多 tasks 同时执行,因此就会有潜在的 Data Races
我们不能直接对外部的变量赋值(这是一个编译时检查错误)
1 | var integers = [Int]() |
Non-Throwing Task Group 方法一致,这里就不再演示了。
这里使用
Task.detached
是为了防止受到 Context 的影响,例如:下载所有商品的缩略图应在其他线程上完成,而非主线程。
往下看就知道我在说什么了。
Task vs. Task.detached
WWDC Session:探索 Swift 中的结构化并发
相关文章:What’s the difference between a task and a detached task?
Task 提供了一种 async 的环境来执行异步操作。
Task: Runs the given nonthrowing operation asynchronously as part of a new top-level task on behalf of the current actor.
Task.detached: Runs the given throwing operation asynchronously as part of a new top-level task.
Task 会继承当前的 Context 和 Priority,
Honestly,这句话我理解了很久,Priority好理解,但是 Context 是什么?
1 |
|
诸如线程之类的属性就是所谓的 Context。
也就是说上层的运行环境会被继承到 Task 中来,如果不想要这种继承,使用 Task.detached
需要注意的是,使用 Task.detached
需要显式捕获 self
,即 self.variable
Task + ObservableObject
WWDC Session:探索 Swift 中的结构化并发
相关文章:What’s the difference between a task and a detached task?
在 SwiftUI 中,很多情况下我们会使用 @ObservedObject
或者 @StateObject
来管理 ViewModel
这时候,该视图就会在 MainActor
下执行。
1 | Task { |
上面的例子中,你会发现控制台中 Task 1 和 Task 2 仍然会按次序执行。
这是因为 @ObservedObject
或者 @StateObject
限制了视图运行在 MainActor 上,
所有的更新都发生在主线程,而 Task 会继承 Context,因此这时候可以使用 Task.detached
1 | Task.detached { |
都换成 Task.detached
之后,Task 1/2 就是一起执行了,也不会都挤在主线程上跑了。
nonisolated
还记得最开始的 actor 吧,
多个函数调用它的时候只有其中一个能执行,其余的等待上一个执行完成再进入,
这被称为隔离,很有效地避免了 Data Races
但是并非 actor 中所有的函数、属性都需要被隔离开。
- 对 actor 扩展 Hashable 协议时,
hash(into hasher: inout Hasher)
不支持async/await
WWDC Session:利用 Swift Actor 保护可变状态
1 | actor MyHashableActor { |
- actor 内部的函数的其中一部分可以并发执行,充分利用多核性能。
WWDC Session: Swift 并发的可视化与优化
1 | actor Compressor { |
执行逻辑(个人理解)
WWDC Session:Swift 并发功能:幕后故事
@MainActor
中的 Task- Task 中的操作均由 主线程 完成,继承
MainActor
- 遇到
await
时, 程序被挂起,待执行的代码被迁移到其他线程(根据 priority 依次执行),主线程释放,供其他需要在主线程运行的代码继续。 await
结果可用之后,仍呆在原地,等待主线程空闲,之后回到主线程,恢复之前的状态继续向下执行。
- Task 中的操作均由 主线程 完成,继承
- 非
@MainActor
中的 Task- Task 中的操作均会在 其他线程 中完成,继承上层的 Context
- 后两点与上一条基本一致
Task.detached
- 会创建一个新的线程来完成操作。
探索 Swift Concurrency (1)