核心数据:父上下文阻止子级

时间:2015-04-27 15:41:55

标签: ios multithreading core-data nsmanagedobjectcontext

我正在使用核心数据在应用中进行一些后台处理。后台处理在子managedObjectContext上完成。上下文初始化:

appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate

// the moc in appDelegate is created with .MainQueueConcurrencyType
mainThreadMOC = appDelegate.managedObjectContext!
backgroundMOC = NSManagedObjectContext(concurrencyType:NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
backgroundMOC?.parentContext = mainThreadMOC

后台处理按以下方法完成:

// download all new transaction log entries
func syncItems() {

... set up the query object for parse

let moc = CoreDataStore.sharedInstance.backgroundMOC

// perform download
moc?.performBlock( {
    self.runQuery(query)   // Download stuff und do some core data work
    })
}

调试器显示块内的所有工作确实都在后台线程中。

当我从主线程调用此函数并立即使用冗长的核心数据操作阻塞主线程(用于测试目的)时,我看到后台线程停止并且仅在主线程空闲时继续执行。

// this is called from a view controller in the main thread

syncItems() // should start to work in background
for i in 0...200 {
    // do some core data work in main thread
}
// syncItems starts to work after the blocking for-loop ends.

为什么会这样?

4 个答案:

答案 0 :(得分:16)

不要使用父子上下文设置。

亲子语境对于任何事情都不是一个好方法。只需使用一个简单的堆栈:两个上下文与一个共享的持久存储协调器。

亲子情境只会增加很多混乱,而不会给你太多任何东西。这是一个相当误解的概念。我希望像mzarra这样的人会停止提倡这种设置。这对社区是一种伤害。

如果您的背景上下文是主上下文的子上下文,则只要后台上下文需要保存,您就必须锁定两者主要和背景上下文。这会阻止UI。并且您必须第二次锁定UI以将这些更改从UI传播到PSC。如果使用后台上下文,则必须将更改合并到主上下文中,但是您只能为当前在该上下文中注册的对象执行操作。如果添加了新对象,或者当前未注册(引用)的更新/删除对象,那么这些对象基本上是无操作。

答案 1 :(得分:0)

当你在评论中说“在主线程中做一些核心数据工作”就是那样 访问mainThreadMOC的工作?

听起来主要的线程工作可能是锁定runQuery需要访问的内容。

尝试将阻止主要线程的测试繁忙工作更改为无法访问Core Data(NSThread.sleepForTimeInterval应该执行的操作)并查看是否允许后台runQuery工作。

如果这就是问题,你需要将主线程重构为不会阻止runQuery中发生的事情。

答案 2 :(得分:0)

我怀疑你的问题是双重的,即使你正在为测试目的而做,

  

冗长的核心数据操作

主线程上的

阻止了用户界面,并根据您的定义backgroundMOC?.parentContext = mainThreadMOC

我建议为多个NSManagedObjectContext创建一个更健壮的结构。

我建议您按照this answer中列出的步骤进行操作。

此外,您还可以专门创建一个额外的MOC来管理您的runQuery ...

discreteMOC = NSManagedObjectContext(concurrencyType:NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
discreteMOC?.parentContext = backgroundMOC //MOCprivate in my previous answer. 

希望这有帮助。

答案 3 :(得分:0)

关于父上下文,不幸的是,上次我检查文档的时候相当简洁,而且在网上你会发现几乎每个人都已经颠倒了流程。 2011年左右有一个关于Core Data的WWDC会议,其中一切都很清楚,如果仔细查看API,它将开始有意义。

孩子不是背景背景 - 它是主要背景。孩子是您通常与之互动的环境。父级是背景上下文。

孩子将更改推送到其父级。这就是为什么父母有一个persistentStoreCoordinator,但是孩子有一个parentContext(它不需要persistentStoreCoordinator)。

因此,子主体是主(UI)队列中的主要上下文。它将更改保存到其父级中。这发生在内存中(快速 - 让UI尽可能响应)。

父上下文然后通过其persistentStoreCoordinator将其更改保存到持久存储中。这就是父母在私人队列中的原因。

lazy var managedObjectContext: NSManagedObjectContext = {
    let parentContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
    parentContext.persistentStoreCoordinator = self.persistentStoreCoordinator

    let managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
    managedObjectContext.parentContext = parentContext
    return managedObjectContext
}()

还有其他优化,例如将.undoManager设置为nil,但这种通用架构可以完美地用于后台保存。

您可能还想放入一个接收完成块/闭包的save方法,立即保存在您的子队列中(如上所述只保存到父队列中),然后调用parentContext的performBlock方法,在该方法中您将拥有它(在其私有队列中)保存到底层持久存储中(相对较慢,但现在是非阻塞),然后调用完成块/闭包(您已经使用GCD设置在主队列上运行,或者否则你从父进程的performBlock方法中回调主队列。

当你不反转建筑时,一切都完美无缺。我不确定它是如何在网上开始的,但这几乎是一个普遍的错误。

祝你好运。