【问题标题】:How to perform CoreData context.count from a background thread?如何从后台线程执行 CoreData context.count?
【发布时间】:2021-10-19 01:24:29
【问题描述】:

目前,以下是我关于 CoreData 的实现。

class CoreDataStack {
    static let INSTANCE = CoreDataStack()
    
    private init() {
    }
    
    private(set) lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "xxx")
        
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // This is a serious fatal error. We will just simply terminate the app, rather than using error_log.
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        
        // So that when backgroundContext write to persistent store, container.viewContext will retrieve update from
        // persistent store.
        container.viewContext.automaticallyMergesChangesFromParent = true
        
        return container
    }()
    
    private(set) lazy var backgroundContext: NSManagedObjectContext = {
        let backgroundContext = persistentContainer.newBackgroundContext()

        backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        
        return backgroundContext
    }()
}


class NSAttachmentRepository {
    static let INSTANCE = NSAttachmentRepository()
    
    private init() {
    }
    
    func isExist(_ name: String) -> Bool {
        let coreDataStack = CoreDataStack.INSTANCE
        let viewContext = coreDataStack.persistentContainer.viewContext
        
        let fetchRequest = NSFetchRequest<NSAttachment>(entityName: "NSAttachment")
        fetchRequest.fetchLimit =  1
        fetchRequest.predicate = NSPredicate(format: "name == %@", name)
        do {
            let count = try viewContext.count(for: fetchRequest)
            if count > 0 {
                return true
            }
        } catch {
            error_log(error)
        }
        
        return false
    }
}

我处理核心数据的策略是

  1. 为了从主线程(UI线程)执行非阻塞写调用,我将使用CoreDataStack.INSTANCE.backgroundContext
  2. 为了从主线程(UI线程)执行阻塞读取调用,我将使用CoreDataStack.INSTANCE.persistentContainer.viewContext

这个工作一直很好,直到我需要执行以下操作

  • 从后台线程(非 UI 线程)执行阻塞读取调用

我们需要运行PHPickerViewControllerDelegateloadFileRepresentation回调中的代码。如果我们在loadFileRepresentation 回调中使用Thread.isMainThread(返回false)进行检查,它会在后台线程中执行。

当我在 Thread.isMainThread 为 false 的函数中调用 NSAttachmentRepository.INSTANCE.isExist(name) 时,我会遇到以下崩溃

CoreData`+[NSManagedObjectContext Multithreading_Violation_AllThatIsLeftToUsIsHonor]:

我尝试通过将代码从使用 coreDataStack.persistentContainer.viewContext 修改为 coreDataStack.backgroundContext 来“解决”问题

func isExist(_ name: String) -> Bool {
    let coreDataStack = CoreDataStack.INSTANCE
    ////let viewContext = coreDataStack.persistentContainer.viewContext
    let backgroundContext = coreDataStack.backgroundContext
    
    let fetchRequest = NSFetchRequest<NSAttachment>(entityName: "NSAttachment")
    fetchRequest.fetchLimit =  1
    fetchRequest.predicate = NSPredicate(format: "name == %@", name)
    do {
        ////let count = try viewContext.count(for: fetchRequest)
        let count = try backgroundContext.count(for: fetchRequest)
        if count > 0 {
            return true
        }
    } catch {
        error_log(error)
    }
    
    return false
}

但是,我仍然遇到同样的崩溃错误。

您知道如何从后台线程执行 CoreData context.count 吗?

【问题讨论】:

  • 你真的确认从主线程写入是一个性能问题吗?你要写多少数据?
  • 您好,我们对写操作完全没问题。我们的目标是执行读取操作。读取操作是从外部后台线程执行的。我更新了我的问题,以解释外部后台线程的来源。
  • 好的,抱歉,我弄错了,但我的问题仍然存在 - 性能有问题吗?如果需要,为什么不直接将代码分派到主队列?除非您正在阅读数百万条记录,否则 Core Data 非常快。
  • 我没有收到错误,对我来说工作正常。您是否尝试通过将 fetch 请求包装到 backgroundContext.performAndWait(_:) 中来确保队列?
  • 您应该使用aContext.perform{}aContext.performAndWait{} 确保里面的代码在上下文的正确线程中完成。

标签: ios swift core-data


【解决方案1】:

仅使用背景上下文是不够的。您需要在其自己的队列上使用该上下文。您检查了您没有在主队列上运行,但您可以在任何队列上,并且后台上下文仅适用于其中一个。当您在错误的队列上使用 Core Data 时,您看到的错误消息就是 Core Data 所说的。

任何时候使用backgroundContext,都需要将代码封装在对performperformAndWait 的调用中,以确保您的代码在后台上下文的队列中运行。由于你的isExist函数是同步的,所以需要使用performAndWait,这样才能在返回前得到结果。

【讨论】:

    【解决方案2】:

    感谢@Tom Harrington 的指点

    这是关于如何解决此类线程问题的代码 sn-p。

    func isExist(_ name: String) -> Bool {
        let coreDataStack = CoreDataStack.INSTANCE
        let viewContext = coreDataStack.persistentContainer.viewContext
        var result = false
        
        viewContext.performAndWait {
            let fetchRequest = NSFetchRequest<NSAttachment>(entityName: "NSAttachment")
            fetchRequest.fetchLimit =  1
            fetchRequest.predicate = NSPredicate(format: "name == %@", name)
            do {
                let count = try viewContext.count(for: fetchRequest)
                if count > 0 {
                    result = true
                } else {
                    result = false
                }
            } catch {
                error_log(error)
            }
        }
    
        return result
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多