假设我有以下功能。
func first() async {
print("first")
}
func second() {
print("second")
}
func main() {
Task {
await first()
}
second()
}
main()
尽管将
first
函数标记为异步没有任何意义,因为它不执行任何异步工作,但仍然是可能的...
我期望即使第一个函数正在等待,它也会被异步调用。
但实际上输出是
first
second
我如何异步调用第一个函数来模仿 GCD 的变体:
DispatchQueue.current.async { first() }
second()
此行为将根据上下文而改变。
如果您从非隔离上下文调用此函数,则
first
和 second
将在单独的线程上运行。在这种情况下,second
任务实际上并不是在等待first
任务,而是存在一场关于哪个任务先完成的竞赛。如果您在 first
任务中执行一些耗时的操作,并且您会看到 second
任务根本不等待,就可以说明这一点。
这引入了
first
和 second
之间的竞赛,并且您无法保证它们将运行哪个顺序。 (在我的测试中,大多数时候它在 second
之前运行 first
,但偶尔仍然可以在 first
之前运行 second
。)
但是,如果您从参与者隔离的上下文中调用此函数,则
first
will 等待 second
在运行之前屈服。
那么,问题是,你真的关心这两个任务以哪个顺序开始吗?如果是这样,您可以通过(显然)在调用
Task { await first() }
之后放置 second
来消除竞争。或者您只是想确保 second
不会等待 first
完成?在这种情况下,这已经是行为,无需更改您的代码。
你问:
如果
需要与await first()
在同一队列上运行,但异步运行,该怎么办? …我只是想[如果它在后台线程上运行]将意味着由于 UI 更新而不是来自主线程而导致崩溃。second()
@MainActor
标记更新 UI 的例程,这将使其在主线程上运行。但请注意,不要将此限定符与耗时任务本身一起使用(因为您不想阻塞主线程),而是将耗时操作与 UI 更新解耦,并将后者标记为 @MainActor
.
例如,下面是一个手动异步计算 π 并在完成后更新 UI 的示例:
func startCalculation() {
Task {
let pi = await calculatePi()
updateWithResults(pi)
}
updateThatCalculationIsUnderway() // this really should go before the Task to eliminate any races, but just to illustrate that this second routine really does not wait
}
// deliberately inefficient calculation of pi
func calculatePi() async -> Double {
await Task.detached {
var value: Double = 0
var denominator: Double = 1
var sign: Double = 1
var increment: Double = 0
repeat {
increment = 4 / denominator
value += sign * 4 / denominator
denominator += 2
sign *= -1
} while increment > 0.000000001
return value
}.value
}
func updateThatCalculationIsUnderway() {
statusLabel.text = "Calculating π"
}
@MainActor
func updateWithResults(_ value: Double) {
statusLabel.text = "Done"
resultLabel.text = formatter.string(for: value)
}
注意:为了确保
calculatePi
的缓慢同步计算不在当前参与者(大概是主要参与者)上运行,我们需要一个“非结构化任务”。具体来说,我们想要一个“独立任务”,即不在当前参与者上运行的任务。正如 Swift 编程语言:并发:任务和任务组 的非结构化并发 部分所述:
初始值设定项。要创建不属于当前参与者的非结构化任务(更具体地称为分离任务),请调用Task.init(priority:operation:)
类方法。Task.detached(priority:operation:)
停止在游乐场中使用并发 - 最小的模拟器,更好的真实设备
您的代码可以在设备和模拟器上正常工作
打印:
second
first
我无法重现你的例子总是得到:先第二次打印
停止使用单个 Print 语句的并发。您无法保证多核设备的速度有多快。您可以做什么来调查它是如何工作的:
我的代码与 DispatchQueue.current.async { first() }
完全相同override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
Task {
await first()
}
second()
}
func first() async {
for i in 0...1000 {
print("🤍 \(i)")
}
}
func second() {
for i in 0...1000 {
print("♥️ \(i)")
}
}
结果将首先是所有黑色红心,然后是所有白色。
如果你什么DispatchQueue.global.async。使用 Task.detached