在Swift中,@MainActor似乎被withCheckedContinuation继承,而不是被其他异步调用继承,这是巧合吗?

ogsagwnx  于 2023-01-29  发布在  Swift
关注(0)|答案(1)|浏览(407)

在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
如果是设计的,我可以从哪里获得更多关于指定位置的信息?

3j86kqsm

3j86kqsm1#

someOtherAsyncFunction是一个async方法,它不与任何特定的参与者隔离,因此可以在任何线程上自由运行它(大概是来自协作线程池的线程之一)但是withCheckContinuation的闭包参数不是async闭包,而是简单的同步闭包,因此,它在启动它的任何线程上运行似乎是完全合乎逻辑的(尽管我在文档中没有看到任何正式的保证)。
尽管如此,值得注意的是,如果您希望特定的异步函数在特定的参与者上运行,与GCD不同,我们从不根据调用者的上下文假设在何处调用某个函数,而是将正在调用的方法隔离到适当的参与者,或者使用某种global actor指定(比如@MainActor),或者在actor中定义async方法,这样就完成了。
相比之下,在GCD中,调用者通常会将一些代码分派到某个特定的队列中,而且随着我们的项目变得越来越复杂,我们有时会引入一些防御性的编程技术,使用dispatchPreconditionassert测试来确保一个方法实际上运行在我们认为是的队列上。
但是Swift并发消除了我们代码中的所有这些假设(以及可能对这些假设的测试),我们只是将一个特定的方法与一个特定的参与者隔离开来,Swift并发从那里开始处理它。
关于这方面的一个实际例子,请参见WWDC 2021视频Swift concurrency: Update a sample app,特别是关于27 minutes in
另外,在你的问题中,你正在检查某个任务或延续运行的线程。但是,Swift并发性可能会挑战我们传统的线程期望(而且在未来的编译器发行版中,您甚至可能根本无法从异步上下文中使用Thread.current),但是如果您对其中的一些细节感兴趣,请参见WWDC 2021视频Swift concurrency: Behind the scenes,它与您的问题没有直接关系,但它确实提供了对Swift并发线程模型的有趣一瞥。

相关问题