是否可以将异步函数传递给 Swift 中的核心数据托管对象上下文?如果不是,那为什么呢?

Is it possible to pass an async function to a Core Data Managed Object Context in Swift? If not, then why?

我四处寻找将异步函数传递给 Core Data 托管对象上下文的方法,但仍然找不到。我怀疑新 async/await 并发模型的某些部分我不理解,但我不知道它是什么。

我想做什么:

// Grab an moc
let moc = container.newBackgroundContext()

// Enter an async context
Task {

    await moc.perform {

        // Get some object
        let obj = moc.object(with: anObjectID)
        
        // This is not possible because NSManagedObjectContext.perform only accepts
        // a synchronous block
        await obj.doSomeLongRunningProcess()

    }
}

这不可能,我觉得很奇怪。我不确定核心数据 api 中是否还没有它,因为 async/await 太新了,或者是否有充分的理由不可能?

像这样将 doSomeLongRunningProcess 包裹在 Task

await moc.perform {
    Task {
        let obj = moc.object(with: anObjectID)
        await obj.doSomeLongRunningProcess()
    }
}

不起作用,因为内部 Task 在不同的线程上获取 运行,最终导致 CoreData 不一致。我有点希望它会继承上下文的线程,但事实并非如此。

我喜欢一种将异步函数传递给 ManagedObjectContext 的方法,但失败了,我想知道为什么它“t/can 不起作用?

您不能直接从 perform 中调用 async 方法。它需要是同步方法,你不会 await.

您问的是:

... but what if I want to call some other async function from within my doSomeLongRunningProcess?

问题是,如果您在 perform 中遇到假设的 await 暂停点,而执行路径将暂停,线程将可以自由执行该执行程序上挂起的其他代码,并且“延续”(暂停点之后的代码)将在 运行 之后。 (await 调用不像 GCD sync 调用,而更像是延续的调度组 notify。)如果那发生在 perform 内部,这个任务理论上可能会被其他任务破坏,这些任务可能会在继续有机会 运行 之前溜进来。关于这些暂停点、延续等的详细信息,请参见Swift concurrency: Behind the scenes

我建议将 doSomeLongRunningProcess 改为 async,并且如果可能的话,从 doSomeLongRunningProcess 中使用 Task { … } 启动任何异步任务。但是你不能让这个方法是 async 并且有 await 个暂停点。

有关将新 async-await 模式与 Swift 并发结合使用的更多信息,请参阅 WWDC 2021 视频 Bring Core Data concurrency to Swift and SwiftUI

我花了一段时间试图解决这个问题。我能想到的是,考虑到 Swift 并发的当前实现,目前这是不可能的。 Rob 的回答基本上仍然是正确的,但如果您想了解更多信息:

有人在 Swift 论坛 How to create an object graph with serialised access using Swift concurrency 上提出了一个类似的问题,其中有详细说明。

您不能保证异步函数会 运行 在给定的执行程序上(目前)。全局 Actors 使它成为可能,但它将您的模型限制为单个 Actor。这意味着您不能让一个实体在主线程上调用异步函数,而另一个实体在后台线程上调用它。

即使您可以使用 Actor 来保证序列化访问,当您遇到等待挂起点时,一切都会在 window 结束,此时执行将移动。

据我所知,Apple 甚至无法将 NSManagedObjectContext 更改为 Actor,因为您仍然不能保证传递给它的异步函数不会打一个等待暂停点。 Swift 并发规范中必须添加一些基本内容。

我的解决方案: 我基本上删除了所有与 NSManagedObjects 交互的 async/await 方法。我将重新评估 when/if Swift 并发允许自定义执行程序等。