mat*_*att 8 async-await swift swift5.5 mainactor
我正在尝试新的 async/await 东西。我的目标是test()在后台运行该方法,所以我使用asyncDetached; 但是在test()我需要在主线程上进行调用时,所以我使用了 MainActor。
(我意识到这可能看起来很复杂,但它是从一个更好的现实世界案例中缩减而来的。)
好的,所以测试代码看起来像这样(在视图控制器中):
override func viewDidLoad() {
super.viewDidLoad()
asyncDetached(priority: .userInitiated) {
await self.test()
}
}
@MainActor func getBounds() async -> CGRect {
let bounds = self.view.bounds
return bounds
}
func test() async {
print("test 1", Thread.isMainThread) // false
let bounds = await self.getBounds()
print("test 2", Thread.isMainThread) // true
}
Run Code Online (Sandbox Code Playgroud)
第一个print说我不在主线程上。这就是我所期望的。
但是,第二个print说,我是在主线程上。那不是我所期望的。
感觉好像我神秘地回到主线程只是因为我调用了一个 MainActor 函数。我以为我会等待主线程,然后在我已经在的后台线程中恢复。
这是一个错误,还是我的期望错了?如果是后者,怎么做我时走出主线程await,但再回来线程我是吗?我认为这正是 async/await 可以简化的事情......?
(在某种程度上,我可以通过在调用asyncDetached之后再次调用来“解决”问题getBounds;但那时我的代码看起来很像嵌套的 GCD,我不得不想知道为什么我要使用 async/await。)
也许我为时过早,但我继续将其作为错误提交:https : //bugs.swift.org/browse/SR-14756。
更多注意事项:
我可以通过更换来解决问题
let bounds = await self.getBounds()
Run Code Online (Sandbox Code Playgroud)
和
async let bounds = self.getBounds()
let thebounds = await bounds
Run Code Online (Sandbox Code Playgroud)
但这似乎不必要地详细说明,并不能使我相信原始现象不是错误。
我也可以通过使用演员来解决这个问题,这看起来是最好的方法。但同样,这并不能说服我我在这里注意到的现象不是错误。
我越来越相信这是一个错误。我刚刚遇到(并报告)了以下内容:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
async {
print("howdy")
await doSomeNetworking()
}
}
func doSomeNetworking() async {
print(Thread.isMainThread)
}
Run Code Online (Sandbox Code Playgroud)
这打印howdy然后true。但是如果我们注释掉第一个打印,它会打印false. 添加或删除打印语句如何改变我们所在的线程?这当然不是故意的。
据我了解,鉴于这都是非常新的,不能保证asyncDetached必须关闭主线程。
在“Swift 并发:幕后花絮”会议中,讨论了调度程序将尝试将事物保留在同一线程上以避免上下文切换。鉴于此,我不知道如何专门避免主线程,但也许我们不应该关心,只要任务取得进展并且永远不会阻塞。
我发现时间戳 (23:18) 解释了不能保证同一线程在等待后会继续执行。https://developer.apple.com/videos/play/wwdc2021/10254/?time=1398
即使您进行测试是为了弄清楚等待@MainActor函数的确切线程行为,您也不应该依赖它。正如 @fullsailor 的回答一样,该语言明确不保证等待后在同一线程上恢复工作,因此这种行为可能会在任何操作系统更新中发生变化。将来,您可能可以使用自定义执行器来请求特定线程,但这目前不在该语言中。有关更多详细信息,请参阅https://github.com/rjmccall/swift-evolution/blob/custom-executors/proposals/0000-custom-executors.md。
此外,它希望不会导致您在主线程上运行的任何问题。有关调度工作原理的详细信息,请参阅https://developer.apple.com/videos/play/wwdc2021/10254/?time=2074 。您不应该担心通过调用函数@MainActor然后执行昂贵的工作来阻塞主线程:如果有更重要的 UI 工作可用,这将在您的工作之前安排,或者您的工作将在另一个线程上运行。如果你特别担心,你可以Task.yield()在你昂贵的工作之前给 Swift 另一个机会将你的工作移出主线程。有关 的更多详细信息,请参阅此处的自愿暂停 。Task.yield()
在您的示例中,Swift 可能认为不值得从主线程切换回上下文,因为它已经存在,但如果主线程更加饱和,您可能会遇到不同的行为。
编辑:
您所看到的行为async let是因为这会产生一个与您正在执行的工作同时运行的子任务。因此,由于该子进程在主线程上运行,因此您的其他代码不是在主线程上运行。有关子任务的更多详细信息,请参阅https://forums.swift.org/t/concurrency-structed-concurrency/41622 。
以下公式有效,并且非常优雅地解决了整个问题,尽管我有点不愿意发布它,因为我不太明白它是如何工作的:
override func viewDidLoad() {
super.viewDidLoad()
Task {
await self.test2()
}
}
nonisolated func test2() async {
print("test 1", Thread.isMainThread) // false
let bounds = await self.view.bounds // access on main thread!
print("test 2", bounds, Thread.isMainThread) // false
}
Run Code Online (Sandbox Code Playgroud)
我测试了await self.view.bounds对wazoo的调用,view访问和bounds访问都在主线程上。这里的指定nonisolated对于确保这一点至关重要。对这个的需求以及随之而来的需求await对我来说非常令人惊讶,但这一切似乎都与 Actor 的性质以及 UIViewController 是 MainActor 的事实有关。