当在后台异步完成保存时,如何保证嵌套上下文中不同线程的获取结果是最新的?

时间:2018-12-31 20:32:18

标签: swift core-data concurrency synchronization nsmanagedobjectcontext

我已阅读以下Behavior differences between performBlock: and performBlockAndWait:? 但是找不到我的问题的答案。

以下代码是从RayWenderlich video中提取的。具体在10:05,代码是类似的东西

class CoreDataStack {
    var coordinator : NSPersistentStoreCoordinator

    init(coordinator: NSPersistentStoreCoordinator){
        self.coordinator = coordinator
    }
    // private, parent, in background used for saving
    private lazy var savingContext : NSManagedObjectContext = {
        let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        moc.persistentStoreCoordinator = coordinator
        return moc
    }()

    lazy var mainManagedObjectedContext : NSManagedObjectContext = {
        let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
        moc.parent = self.savingContext
        return moc
    }()

    func saveMainContext() {
        guard savingContext.hasChanges || mainManagedObjectedContext.hasChanges else {
            return
        }

        mainManagedObjectedContext.performAndWait {
            do {
                try mainManagedObjectedContext.save()
            }catch let error{
                fatalError(error.localizedDescription)
            }
        }

        savingContext.perform {
            do {
                try self.savingContext.save()
            }catch let error{
                fatalError(error.localizedDescription)
            }
        }
    }
}

据我了解,发生的事情是主上下文只是将更改传递到其父上下文(即私有背景上下文)。它同步执行此操作。

然后,父级私有上下文在异步后台对sqlite进行实际保存。长话短说,这对我们的表现有很大帮助。但是数据完整性呢?!

想象一下我是否要这样做:

let coredataManager = CoreDataStack()
coredataManager.saveMainContext() // save is done asynchronously in background queue
coredataManager.mainManagedObjectedContext.fetch(fetchrequest) 

如何保证我的抓取正在读取最新和更新的结果?

如果我们异步进行写入,那么是否不可能同时进行另一次读取会导致意外结果,即保存更改的结果可能存在或可能不存在?

编辑: 我对以下代码进行了改进。我可以将保存内容输入completionHandler参数中。但这并不能解决整个问题。如果我是从mainQueue发出fetchRequest的,而其他地方却不知道同时进行保存?

enum SaveStatus{
    case noChanges
    case failure
    case success
}


func saveMainContext(completionHandler: (SaveStatus -> ())) {
    guard savingContext.hasChanges || mainManagedObjectedContext.hasChanges else {
        completionHandler(.noChanges)
        return
    }

    mainManagedObjectedContext.performAndWait {
        do {
            try mainManagedObjectedContext.save()
        }catch let error{
            completionHandler(.failure)
            fatalError(error.localizedDescription)
        }
    }

    savingContext.perform {
        do {
            try self.savingContext.save()
            completionHandler(.succes)
        }catch let error{
            completionHandler(.failure)
            fatalError(error.localizedDescription)
        }
    }
}

2 个答案:

答案 0 :(得分:0)

SELECT Claims.* FROM claims Claims INNER JOIN ( SELECT number, MAX(rev_number) AS latest FROM claims WHERE project_id = 1 GROUP BY number) AS GroupedClaims ON GroupedClaims.number = Claims.number AND GroupedClaims.latest = Claims.rev_number; 的所有调用将是同步的,因此将被阻止。如果您调用INNER JOIN (SELECT ... ),然后立即调用mainManagedObjectContext,则即使保存/获取请求来自不同的队列,获取请求也不会通过,直到保存请求完成为止(请参见您上面的链接)。

执行提取请求时,您不是从持久性存储中拉出-您是从刚刚更新的子容器中拉出。您无需等待更改提交到持久性存储,因为您无需从那里访问数据。子容器将为您提供最新的更改。

子容器容器-它会将您的最新更改保存在内存中(而不是存储在磁盘上-这是持久性容器的工作)。

这里的真正问题是您的saveMainContext()应该实现单例模式,以防止实例化同一容器的多个版本(从技术上讲,它们仍在同一线程上并因此被序列化,但访问这些容器不会是线程安全的)。换句话说,每次实例化mainManagedObjectedContext.fetch(fetchrequest)时,您都在创建一个新的CoreDataStackCoreDataStack()

相反,只需实例化一次。

savingContext

然后这样呼叫:

mainManagedObjectedContext

(请参阅this link回复:“孩子和父母有相同的对象吗?”)

唯一的一个子代不会与父代同步的情况是,您有多个子代访问同一个父代-但这似乎不是这种情况。

答案 1 :(得分:0)

该问题并非特定于核心数据。

这是经典的读写问题。

保护数据源的常用方法是使用串行队列访问数据源。否则,如果没有串行队列,您将遇到读写问题。

在以下示例中:

let coredataManager = CoreDataStack() // 1
coredataManager.saveMainContext() // 2 save is done asynchronously in background queue
coredataManager.mainManagedObjectedContext.fetch(fetchrequest) // 3

coredataManager要从串行队列访问。因此,即使第二行中的写入操作是异步完成的,第3行中的读取操作也必须等待,直到 serial 队列被解除阻塞。