考虑以下相对简单的 Swift 程序:
import Foundation
func printContext(function: String = #function, line: Int = #line) {
print("At \(function):\(line): Running on \(Thread.current) (main: \(Thread.isMainThread))")
}
printContext()
Task { @MainActor in
printContext()
}
Task.detached { @MainActor in
printContext()
}
Task {
await MainActor.run {
printContext()
}
}
DispatchQueue.main.async {
printContext()
}
dispatchMain()
根据全球参与者提案,我预计
DispatchQueue.main.async { ...
大致相当于Task.detached { @MainActor in ...
。
然而,在
arm64-apple-macosx12.0
上的 Swift 5.6.1 中,该程序似乎在调用时不确定地产生不同的结果。有时我会得到预期的输出:
At main:7: Running on <_NSMainThread: 0x600000083c80>{number = 1, name = main} (main: true)
At main:10: Running on <_NSMainThread: 0x600000083c80>{number = 1, name = main} (main: true)
At main:19: Running on <_NSMainThread: 0x600000083c80>{number = 1, name = main} (main: true)
At main:14: Running on <_NSMainThread: 0x600000083c80>{number = 1, name = main} (main: true)
At main:24: Running on <_NSMainThread: 0x600000083c80>{number = 1, name = main} (main: true)
有时,
@MainActor
闭包似乎在另一个线程上执行:
At main:7: Running on <_NSMainThread: 0x600002ae44c0>{number = 1, name = main} (main: true)
At main:24: Running on <_NSMainThread: 0x600002ae44c0>{number = 1, name = main} (main: true)
At main:10: Running on <NSThread: 0x600002afff00>{number = 2, name = (null)} (main: false)
At main:19: Running on <NSThread: 0x600002afff00>{number = 2, name = (null)} (main: false)
At main:14: Running on <NSThread: 0x600002afff00>{number = 2, name = (null)} (main: false)
只有
DispatchQueue
机制似乎可以可靠地调度到 main
线程上。我是否误解了并发模型的一部分,或者为什么程序会这样?
是的,async/await 抽象了线程。在幕后,有一个线程池,当您运行任务时,您基本上是说,当有可用时间(并给予优先级)时,运行此代码。您的代码可能会在一个线程上挂起,然后在同一任务中的另一个线程上恢复。
因此,任务中运行的代码可以在随机线程上运行。要在主线程上运行代码,您需要使用
await MainActor.run
。否则,你没有保证。
全局参与者(包括主要参与者)进行了优化,如果部分任务不执行任何实际需要参与者隔离的操作,则它们可以消除不必要的执行器跳跃。最终效果是代码并不总是在您可能期望的线程上运行。
但是,如果您更改示例,使其明确要求参与者隔离(例如,可能增加与主要参与者隔离的计数器),那么这种行为将会消失。