我首先要说的是,关于此问题有很多相似但不同的帖子,主要是因为它们提供的示例而难以理解。
所以,我将提供更容易理解的示例代码。
struct ContentView: View {
@StateObject private var viewModel = ViewModel()
var body: some View {
Button("Test") {
viewModel.runLongTaskOnMainThread()
}
}
}
class ViewModel: ObservableObject {
@MainActor
func runLongTaskOnMainThread() {
Task {
await asyncLongTask()
}
}
private func asyncLongTask() async {
try? await Task.sleep(for: .seconds(5))
print(#function)
}
}
我遇到的问题是,我预计点击按钮后,方法
asyncLongTask()
会阻塞主线程,但事实并非如此。
我检查了where它正在执行,发现它在后台线程中。
我认为通过用
runLongTaskOnMainThread()
标记 @MainActor
,可以保证其中运行的任何内容都将在主线程中运行。
然后我意识到这是真的,但这一定意味着任务的创建发生在主线程中,而不是其操作的执行(注意
Task(operation: () -> Success)
。
所以,我认为将函数重新定义为
@MainActor
func runLongTaskOnMainThread() {
Task { @MainActor in
await asyncLongTask()
}
}
然后 会阻塞主线程,但同样不会,因为
asyncLongTask()
是在后台线程中执行的。
然后我发现我能保证它在主线程中运行的唯一方法就是用 @MainActor 标记它:
@MainActor
private func asyncLongTask() async {
try? await Task.sleep(for: .seconds(5))
print(#function)
}
这是怎么回事?显然我误解了非结构化任务如何继承其调用者的执行上下文。
我的一个假设是,因为任务内部有一个挂起点(
await asyncLongTask()
),这意味着它可以在不同的线程上恢复执行。意思是,如果有同步代码,它们将在主线程中执行,而不是在asyncLongTask()
中执行。
asyncLongTask
调用Task.sleep
,它不会阻塞调用者线程(与旧版sleep
API不同)。正如 sleep(nanoseconds:)
文档 所说:
该函数不会阻塞底层线程。
所有
Task.sleep
方法都不起作用。