在Swift方法中,指定了@MainActor
,调用withCheckedContinuation
并进行其他异步调用,看起来withCheckedContinuation
在@MainActor
线程上被调用,但其他异步调用不是。
这是巧合吗?
以下代码可以放入XCode为您创建的默认iOS App的ContentView.swift中:
import SwiftUI
func printCurrentThread(_ label: String) {
print("\(label) called on \(Thread.current)")
}
class SomeNonThreadSafeLibrary {
func getFromServer(msg: String, completionHandler: @escaping (String) -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
completionHandler(msg)
}
}
}
class AppController: ObservableObject {
@Published var something1: String = "not set"
@Published var something2: String = "not set"
var someNonThreadSafeLibrary: SomeNonThreadSafeLibrary? = SomeNonThreadSafeLibrary()
func someOtherAsyncFunction(calledFrom: String) async {
printCurrentThread("\(calledFrom) -> someOtherAsyncFunction")
}
func getSomethingAsynchronously() async -> String {
printCurrentThread("getSomethingAsynchronously")
await someOtherAsyncFunction(calledFrom: "getSomethingAsynchronously")
return await withCheckedContinuation { continuation in
printCurrentThread("getSomethingAsynchronously -> withCheckedContinuation")
do {
try Task.checkCancellation()
self.someNonThreadSafeLibrary?.getFromServer(msg: "getSomethingAsynchronously") { result in
printCurrentThread("getSomethingAsynchronously -> getFromServer closure")
continuation.resume(returning: result)
}
}
catch {}
}
}
@MainActor
func getSomethingAsynchronouslyWithMainActor() async -> String {
printCurrentThread("getSomethingAsynchronouslyWithMainActor")
await someOtherAsyncFunction(calledFrom: "getSomethingAsynchronouslyWithMainActor")
return await withCheckedContinuation { continuation in
printCurrentThread("getSomethingAsynchronouslyWithMainActor -> withCheckedContinuation")
do {
try Task.checkCancellation()
self.someNonThreadSafeLibrary?.getFromServer(msg: "getSomethingAsynchronouslyWithMainActorWithMainActor") { result in
printCurrentThread("getSomethingAsynchronouslyWithMainActor -> getFromServer closure")
continuation.resume(returning: result)
}
}
catch {}
}
}
@MainActor
func getValueForSomething() async {
something1 = await getSomethingAsynchronously()
}
@MainActor
func getValueForSomethingWithMainActor() async {
something2 = await getSomethingAsynchronouslyWithMainActor()
}
}
struct ContentView: View {
@StateObject private var appController = AppController()
var body: some View {
VStack {
Text("something1 is \(appController.something1)")
Text("something2 is \(appController.something2)")
}
.task {
await appController.getValueForSomething()
}
.task {
await appController.getValueForSomethingWithMainActor()
}
.padding()
}
}
代码的输出如下所示...
带@MainActor
的func在线程1上调用,不带@MainActor
的func在任意线程上调用,如预期:
getSomethingAsynchronouslyWithMainActor called on <_NSMainThread: 0x600001910400>{number = 1, name = main}
getSomethingAsynchronously called on <NSThread: 0x60000195c300>{number = 7, name = (null)}
然后从这些方法中的每一个调用不做任何事情的func someOtherAsyncFunction
,在这两种情况下(即调用函数上有或没有@MainActor
),它会在任意线程上被调用,这意味着函数上的@MainActor
不会被它内部调用的函数继承:
getSomethingAsynchronously -> someOtherAsyncFunction called on <NSThread: 0x60000195c300>{number = 7, name = (null)}
getSomethingAsynchronouslyWithMainActor -> someOtherAsyncFunction called on <NSThread: 0x6000019582c0>{number = 6, name = (null)}
然而,在withCheckedContinuation
闭包的特定情况下,当@MainActor
在调用函数上时,它似乎确实在线程1上运行:
getSomethingAsynchronously -> withCheckedContinuation called on <NSThread: 0x60000195c300>{number = 7, name = (null)}
getSomethingAsynchronouslyWithMainActor -> withCheckedContinuation called on <_NSMainThread: 0x600001910400>{number = 1, name = main}
那么...... withCheckedContinuation
似乎通过其调用函数运行在@MainActor
上,这仅仅是一个巧合,还是设计使然?为什么它只是withCheckedContinuation
,而不是someOtherAsyncFunction
?
如果是设计的,我可以从哪里获得更多关于指定位置的信息?
1条答案
按热度按时间3j86kqsm1#
someOtherAsyncFunction
是一个async
方法,它不与任何特定的参与者隔离,因此可以在任何线程上自由运行它(大概是来自协作线程池的线程之一)但是withCheckContinuation
的闭包参数不是async
闭包,而是简单的同步闭包,因此,它在启动它的任何线程上运行似乎是完全合乎逻辑的(尽管我在文档中没有看到任何正式的保证)。尽管如此,值得注意的是,如果您希望特定的异步函数在特定的参与者上运行,与GCD不同,我们从不根据调用者的上下文假设在何处调用某个函数,而是将正在调用的方法隔离到适当的参与者,或者使用某种global actor指定(比如
@MainActor
),或者在actor
中定义async
方法,这样就完成了。相比之下,在GCD中,调用者通常会将一些代码分派到某个特定的队列中,而且随着我们的项目变得越来越复杂,我们有时会引入一些防御性的编程技术,使用
dispatchPrecondition
或assert
测试来确保一个方法实际上运行在我们认为是的队列上。但是Swift并发消除了我们代码中的所有这些假设(以及可能对这些假设的测试),我们只是将一个特定的方法与一个特定的参与者隔离开来,Swift并发从那里开始处理它。
关于这方面的一个实际例子,请参见WWDC 2021视频Swift concurrency: Update a sample app,特别是关于27 minutes in。
另外,在你的问题中,你正在检查某个任务或延续运行的线程。但是,Swift并发性可能会挑战我们传统的线程期望(而且在未来的编译器发行版中,您甚至可能根本无法从异步上下文中使用
Thread.current
),但是如果您对其中的一些细节感兴趣,请参见WWDC 2021视频Swift concurrency: Behind the scenes,它与您的问题没有直接关系,但它确实提供了对Swift并发线程模型的有趣一瞥。