使用 Swift 中的新异步任务从另一个线程正确访问核心数据

Correctly accessing Core Data from another thread using the new Asynchronous Task in Swift

为了测试我的 Core Data 实现,我启用了启动参数 com.apple.CoreData.ConcurrencyDebug 1。每当我使用 Swifts new async APIs.

Task 中访问托管对象时,我都会触发断点

我使用单个上下文 (viewContext) 来获取和临时后台上下文来执行写入操作。在底部查看重要部分的一些片段。

我的应用程序运行完美,除了我从 Task.

中访问 Core Data 托管对象的情况外,我没有触发任何断点

在此处查看示例

func performReloadForecasts() {
    Task {
        await reloadForecasts()
    }
}

方法 reloadForecasts 对 post 来说太长了,但它引用了用户最喜欢的位置(因此 locations)并使用它从 API 该位置。 在视图或视图模型中引用用户位置功能正常。

但据我所知,通过使用 Task 执行异步操作,我在 [=66= 处(从池中?)选择的一个完全不同的线程中执行任务] 时间。

通常 Task 是 运行 的线程是 com.apple.root.user-initiated-qos.cooperative (serial) 我想这是有道理的。

我如何重构或更改我的异步函数(例如 reloadForecast)或我的核心数据堆栈(详见下文)以在遵守核心数据并发规则的庄园中执行操作?

我可以在特定线程上强制任务 运行 吗?由于我的主要上下文是 viewContext 它必须是主线程,这有点违背异步 Task.

的目的

我能否重构我的核心数据堆栈以创建对我的托管对象的某种线程安全引用?我看到过将对象 ID 传入并在目标线程中重新获取对象的建议,但肯定有更优雅的方式来遵守并发规则。

核心数据实现

上下文设置

container = NSPersistentCloudKitContainer(name: "Model")

context = container.viewContext
context.automaticallyMergesChangesFromParent = true
context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy

正在获取

@Published private var locations: [LocationModel] = []
private func reloadData() {
    context.perform { [context] in
        do {
            self.locations = try context.fetch(LocationModel.fetchRequest())
        } catch {
            Logger.error("Failed to reload persistence layer.")
            Logger.error(error.localizedDescription)
        }
    }
}

执行写入操作

func perform(_ block: @escaping (NSManagedObjectContext) -> Void) {
    do {
        let context = container.newBackgroundContext()

        try context.performAndWait {
            block(context)
            try context.save()
        }
    } catch {
        Logger.error(error.localizedDescription)
    }
}

我通过将 @MainActor 标志添加到 Task

来解决其中的一些问题
    Task { @MainActor in
        await reloadForecasts()
    }

但是我仍然遇到某些问题的断点,尤其是映射或排序等高阶函数。我最终将 @MainActor 包装器添加到我所有的视图模型中。

这修复了所有关于访问核心数据对象的高阶函数的奇怪崩溃,但我遇到了新问题。我用于保存对象的第二个核心数据上下文现在是触发并发断点的原因。

这更有意义,而且 debug-able。我对在主上下文中获取的对象有很强的引用,用于在后台上下文中构造另一个对象。

Model A <---> Model B

我有 Model A 与另一个 Model B 有关系。为了设置与 Model B 的关系,我使用了对在主上下文中获取的 Model B 对象的引用。但是我在后台线程中创建了 Model A 对象。

为了解决这个问题,我使用了建议的方法,在正确的上下文中通过 ObjectID 重新获取所需的对象。 (使用一堆不错的辅助方法使事情变得更容易)

这里有一个论坛 post 询问有关确保异步任务 运行 在主线程上的相关问题

https://forums.swift.org/t/best-way-to-run-an-anonymous-function-on-the-main-actor/50083

我对新的 swift 并发模型的理解是,当您 await 在异步函数上时,任务是 运行 在从池中选择的另一个线程上,并且当任务已完成,执行 returns 到您使用 await.

的点(和线程)

在这种情况下,我强制任务在主线程上启动(通过使用@MainActor),在可用池中的一个线程上执行它的任务,并且 return 一旦它返回到主线程完成。

swift 并发性详细解释了其中的一些内容:https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html