问题描述
我正在试用新的异步/等待功能。我这里的目标是在后台运行test()
方法,所以我使用Task.detached
;但是在test()
期间,我需要调用主线程,所以我使用MainActor。
(我知道孤立地看起来可能很复杂,但它比现实世界中的情况要好得多。)
好的,测试代码如下所示(在视图控制器中):
override func viewDidLoad() {
super.viewDidLoad()
Task.detached(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
}
第一个print
表示我不在主线程上。这正是我所期待的。
但第二个print
表示我在主线程上。这不是我所期望的。
感觉好像就因为我调用了MainActor函数,就神秘地回到了主线程中。我以为我将等待主线程,然后在我已经在的后台线程中继续。
这是一个错误,还是我的预期错了?如果是后者,我如何在await
期间跳到主线程,但随后又返回到我所在的线程?我认为这正是异步/等待将使之变得简单...?
(在某种程度上,我可以通过在调用getBounds
之后再次调用Task.detached
来解决这个问题;但在这一点上,我的代码看起来非常像嵌套的GCD,以至于我不得不想知道我为什么要使用异步/等待。)
也许我为时过早,但我将其作为错误提交:https://bugs.swift.org/browse/SR-14756。
更多备注:
我可以通过替换
来解决问题 let bounds = await self.getBounds()
与
async let bounds = self.getBounds()
let thebounds = await bounds
但这似乎是不必要的详细说明,也不能让我相信最初的现象不是一个错误。
我也可以通过使用演员来解决这个问题,这开始看起来是最好的方法。但同样,这并不能说服我相信我在这里提到的现象不是一个错误。
我越来越确信这是一个错误。我刚刚遇到(并报告)以下情况:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
async {
print("howdy")
await doSomeNetworking()
}
}
func doSomeNetworking() async {
print(Thread.isMainThread)
}
这将打印howdy
,然后第二个print
打印true
。但是如果我们注释掉第一个打印,剩下的(第二个)print
打印false
!
仅仅添加或删除一条print语句就能改变我们所处的线程吗?这肯定不是故意的。
推荐答案
以下公式很好地解决了整个问题,但我不太愿意发布,因为我不太明白它是如何工作的:
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
}
我测试了await self.view.bounds
调用wazoo,view
访问和bounds
访问都在主线程上。这里的nonisolated
是确保这一点的关键。对此的需求以及伴随而来的await
需求对我来说非常令人惊讶,但这似乎都与参与者的性质以及UIView控制器是MainActor的事实有关。