我很好奇为什么Task C和Task D不在主线程上执行,与Task A和Task B不同。我理解任务从其父级继承上下文,在这种情况下,它们都共享同一个父级。是否可以假设调用函数会导致创建detached任务?
struct ThreadTestView: View {
var body: some View {
Text("Test Thread")
.onAppear {
// Task A
print("Task A - \(threadInfo())")
// Task B
Task {
print("Task B - \(threadInfo())")
}
// Task C
printInfo("Task C")
// Task D
Task {
printInfo("Task D")
}
}
}
func printInfo(_ source: String) {
Task {
print("\(source) - \(threadInfo())")
}
// detached
Task.detached {
print("\(source) - detached \(threadInfo())")
}
}
func threadInfo() -> String {
"isMain:\(Thread.isMainThread) - \(Thread.current)"
}
}
#Preview {
ThreadTestView()
}
控制台输出:
Task A - isMain:true - <_NSMainThread: 0x280694040>{number = 1, name = main}
Task C - isMain:false - <NSThread: 0x2806c6b00>{number = 8, name = (null)}
Task C - detached isMain:false - <NSThread: 0x2806c4580>{number = 5, name = (null)}
Task B - isMain:true - <_NSMainThread: 0x280694040>{number = 1, name = main}
Task D - isMain:false - <NSThread: 0x2806c6b00>{number = 8, name = (null)}
Task D - detached isMain:false - <NSThread: 0x2806c6b00>{number = 8, name = (null)}
body
与主要参与者隔离(因此在主线程上运行)。显然 A 将在主线程上运行。 B 启动 Task {…}
非结构化并发,这会在当前参与者(即本例中的主要参与者)上创建一个新的顶级任务,因此它也运行在主线程上。
然而,C 和 D 不在主线程上,因为 printInfo
不是参与者隔离的,因此,无论您如何启动非结构化并发,生成的任务也不是参与者隔离的。因此,由
printInfo
创建的任务在协作线程池中的线程上运行,而不是在主线程上运行。
printInfo
方法与主要参与者隔离(要么显式地以
@MainActor
本身为前缀,或者是与主要参与者及其所有成员隔离的类型的成员),则故事将会改变。然后它的
Task {…}
将被隔离到同一个 actor,而
Task.detached {…}
仍然不会被隔离到任何 actor,因此将在协作线程池中的随机线程上运行。
巨大的免责声明:我仍在填补自己对 Swift Concurrency 理解的空白,因此我很高兴以下任何陈述被证明是错误的。我认为这里重要的一点是“主线”与“主角”不是一回事。两者之间存在一些差异,但一个显着的差异是参与者是一种编译时构造,而线程主要是一种运行时构造。仅仅因为代码在主线程上运行并不意味着它是“主要参与者隔离的”。查看
onAppear(perform:) 方法的文档,您可以看到 perform
闭包不包含
@MainActor
注释,因此它不是主要参与者隔离的,尽管我们可以非常有信心它将在主线程上调用。因此,当您从该闭包内部实例化
Task
时(尽管
Task.init
继承了当前 actor),闭包本身并不是与 actor 隔离的,因此没有可以继承的 actor。执行器可以自由地将此任务安排在协作线程池中可用的任何线程上,甚至可以安排在主线程上。尝试用
printInfo
注释您的
@MainActor
函数,看看情况如何变化。
您的视图 (
struct ThreadTestView: View
) 未注释为
@MainActor
。协议视图本身也没有用
@MainActor
注释。仅
body
内的
protocol View
用
@MainActor
注释:
所以我认为这种行为源于这 3 个事实。
@ViewBuilder @MainActor var body: Self.Body { get }
首先,我们不仅应该监视
printInfo
中生成的任务,还应该监视函数本身的调用位置:
func printInfo(_ source: String) {
// THIS: where the function itself is called
print("\(source) - function itself \(threadInfo())")
Task {
print("\(source) - \(threadInfo())")
}
// detached
Task.detached {
print("\(source) - detached \(threadInfo())")
}
}
那么我在这里期待什么:在所有 4 种情况下,
printInfo
本身(我在上面添加的新行)都将在主线程上调用。这是因为所有以
body
作为父项(用
@MainActor
注释)的项目都将保留该上下文。
printInfo
生成的,正如我们所建立的,它不是 @MainActor。因此,该任务也不是主要参与者。
body
内部生成的,它将继承主参与者上下文(因此函数
printBody
将在主线程上运行) - 与情况 B 非常相似。但是第二个任务是从该函数生成的
printInfo
不会出现在主线程上,就像情况 C 一样。
这里和这里