当他们说一个NSManagedObjectContext是由创建它的线程或队列拥有时,Apple是什么意思? [英] What does Apple mean when they say that a NSManagedObjectContext is owned by the thread or queue that created it?

查看:141
本文介绍了当他们说一个NSManagedObjectContext是由创建它的线程或队列拥有时,Apple是什么意思?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

似乎在11月,Apple更新了 NSManagedObjectContext类参考处理数据编程指南文档,以明确保护串行GCD调度队列和NSOperationQueue作为同步访问的可接受机制, NSManagedObjectContext 。但他们的建议似乎含糊不清,可能是矛盾的,我想确保我已正确理解它。



以前接受的智慧似乎是一个 NSManagedObjectContext 只能从创建它的线程访问,并且使用串行队列进行同步是不够的;虽然串行队列一次只执行一个操作,但是这些操作可以在不同的线程上被调度,并且MOC不喜欢这样。



但是现在,从编程指南,我们有:


您可以使用线程,串行操作队列或调度队列来实现并发。为了简洁,本文使用线程来指代任何这些。


到目前为止,它们的线程和队列的混淆是无益的)。所以我可以安全地使用单个上下文每个(串行)队列,而不是每个操作/块一个,对吧?苹果甚至在核心数据WWDC会话中有一个可视化的描述。



但是,你在哪里创建队列的上下文?在 NSManagedObjectContext 文档中,Apple状态:


owner是分配它的线程或队列 - 这由调用其init方法的线程确定。因此,您不应该在一个线程上初始化上下文,然后将其传递给不同的线程。


现在我们有 NSManagedObjectContext 需要知道其所有者是谁。我假设这意味着在队列中执行的第一个操作应该创建MOC并保存对它的引用以供剩余的操作使用。



这是对?我不屑一顾的唯一原因是 NSManagedObjectContext 文章继续说:


相反,你应该传递一个引用到持久存储协调器,并让接收线程/队列创建一个新的上下文。如果使用NSOperation,则必须在main(对于串行队列)或start(对于并发队列)中创建上下文。


苹果现在似乎与排队执行的队列混合操作。这使我的头,让我想知道他们真的想要你只是为每个操作创建一个新的MOC。 NSManagedObjectContext和任何与其关联的托管对象应该固定到单个actor(线程,序列化队列,NSOperationQueue with max concurrency = 1)。



这种模式称为线程限制或隔离。没有一个伟大的短语为(线程||序列化队列|| NSOperationQueue与max concurrency = 1),所以文档继续说,我们将使用'线程'的核心数据文档的剩余部分,当我们意味着任何一种获得序列化控制流的三种方式



如果你在一个线程上创建一个MOC,然后在另一个线程上使用它,你就会通过暴露MOC对象引用两个线程。简单。不要这样做。不要跨越流。



我们明确地调用NSOperation,因为不像线程& GCD,它有这个奇怪的问题,其中-init运行在创建NSOperation的线程,但-main运行在运行NSOperation的线程。这是有道理的,如果你眯着眼睛正确,但它不直观。如果你在 - [NSOperation init]中创建你的MOC,那么NSOperation将有助于违反线程限制,在你的-main方法甚至运行和你被淹没。



我们以任何其他方式积极阻止/弃用使用MOC和线程。虽然理论上可能做什么bbum提到,没有人得到这个权利。每个人都跳了起来,忘记了一个必要的调用,在1个地方,init运行在哪里?,或以其他方式解决自己。有了自动释放池和应用程序事件循环,以及undo管理器和可可绑定和KVO,只有这么多方法,一个线程持有对MOC的引用,当你试图传递到其他地方。它甚至比先进的Cocoa开发人员想象的要困难得多,直到他们开始调试。所以这不是一个非常有用的API。



文档改变了,以澄清和强调线程限制模式作为唯一理智的方式去。你应该考虑试图在NSManagedObjectContext上使用-lock和-unlock,以便(a)不可能和(b)事实上不推荐使用。它不是字面上不赞成,因为代码工作和以前一样。但是你的代码使用它是错误的。



有些人在1个线程上创建MOC,并将它们传递给另一个没有调用锁。这从来不合法。创建MOC的线程一直是MOC的默认所有者。这成为在主线程上创建的MOC的更频繁的问题。主线程MOC与应用程序的主事件循环交互,用于撤销,内存管理和一些其他原因。在10.6和iOS 3上,MOC对主线程拥有更强大的优势。



虽然队列不绑定到特定的线程,如果你创建一个MOC队列的上下文正确的事情会发生。您的义务是遵循公共API。



如果队列被序列化,您可以与在该队列上运行的后续块共享MOC。



所以不要在任何情况下将NSManagedObjectContext *暴露给多个线程(actor等)。有一个歧义。你可以将NSNotification *从didSave通知传递给另一个线程的MOC的-mergeChangesFromContextDidSaveNotification:方法。






It seems that in November, Apple updated both the NSManagedObjectContext Class Reference and the Core Data Programming Guide documents to explicitly bless serial GCD Dispatch Queues and NSOperationQueues as acceptable mechanisms for synchronising access to a NSManagedObjectContext. But their advice seems ambiguous and possibly contradictory, and I want to make sure I've understood it properly.

Previously the accepted wisdom seemed to be that a NSManagedObjectContext could only be accessed from the thread that created it, and that using a serial queue for synchronisation was not sufficient; although serial queues only perform one operation at a time, these operations can potentially be scheduled on different threads, and a MOC doesn't like that.

But now, from the programming guide, we have:

You can use threads, serial operation queues, or dispatch queues for concurrency. For the sake of conciseness, this article uses "thread" throughout to refer to any of these.

So far, so good (although their conflation of threads and queues is unhelpful). So I can safely use a single context per (serial) queue, instead of one per operation/block, right? Apple even has a visual depiction of this in the Core Data WWDC sessions.

But... where do you create the context for the queue? In the NSManagedObjectContext documentation, Apple state:

[A context] assumes the default owner is the thread or queue that allocated it—this is determined by the thread that calls its init method. You should not, therefore, initialize a context on one thread then pass it to a different thread.

So now we have the idea of a NSManagedObjectContext needing to know who its owner is. I'm assuming this means that the first operation to be executed in the queue should create the MOC and save a reference to it for the remaining operations to use.

Is this right? The only reason I'm hesitant is that the NSManagedObjectContext article goes on to say:

Instead, you should pass a reference to a persistent store coordinator and have the receiving thread/queue create a new context derived from that. If you use NSOperation, you must create the context in main (for a serial queue) or start (for a concurrent queue).

Apple now seem to be conflating operations with the queues that schedule their execution. This does my head in, and makes me wonder if they really do want you to just create a new MOC for every operation after all. What am I missing?

解决方案

The NSManagedObjectContext and any managed objects associated with it should be pinned to a single actor (thread, serialized queue, NSOperationQueue with max concurrency = 1).

This pattern is called thread confinement or isolation. There isn't a great phrase for (thread || serialized queue || NSOperationQueue with max concurrency = 1) so the documentation goes on to say "we'll just use 'thread' for the remainder of the Core Data doc when we mean any of those 3 ways of getting a serialized control flow"

If you create a MOC on one thread, and then use it on another, you have violated thread confinement by exposing the MOC object reference to two threads. Simple. Don't do it. Don't cross the streams.

We call out NSOperation explicitly because unlike threads & GCD, it has this odd issue where -init runs on the thread creating the NSOperation but -main runs on the thread running the NSOperation. It makes sense if you squint at it right, but it is not intuitive. If you create your MOC in -[NSOperation init], then NSOperation will helpfully violate thread confinement before your -main method even runs and you're hosed.

We actively discourage / deprecated using MOCs and threads in any other ways. While theoretically possible to do what bbum mentions, no one ever got that right. Everybody tripped up, forgot a necessary call to -lock in 1 place, "init runs where ?", or otherwise out-clevered themselves. With autorelease pools and the application event loop and the undo manager and cocoa bindings and KVO there are just so many ways for one thread to hold on to a reference to a MOC after you've tried to pass it elsewhere. It is far more difficult than even advanced Cocoa developers imagine until they start debugging. So that's not a very useful API.

The documentation changed to clarify and emphasize the thread confinement pattern as the only sane way to go. You should consider trying to be extra fancy using -lock and -unlock on NSManagedObjectContext to be (a) impossible and (b) de facto deprecated. It's not literally deprecated because the code works as well as it ever did. But your code using it is wrong.

Some people created MOCs on 1 thread, and passed them to another without calling -lock. That was never legal. The thread that created the MOC has always been the default owner of the MOC. This became a more frequent issue for MOCs created on the main thread. Main thread MOCs interact with the application's main event loop for undo, memory management, and some other reasons. On 10.6 and iOS 3, MOCs take more aggressive advantage of being owned by the main thread.

Although queues are not bound to specific threads, if you create a MOC within the context of a queue the right things will happen. Your obligation is to follow the public API.

If the queue is serialized, you may share the MOC with succeeding blocks that run on that queue.

So do not expose an NSManagedObjectContext* to more than one thread (actor, etc) under any circumstance. There is one ambiguity. You may pass the NSNotification* from the didSave notification to another thread's MOC's -mergeChangesFromContextDidSaveNotification: method.

  • Ben

这篇关于当他们说一个NSManagedObjectContext是由创建它的线程或队列拥有时,Apple是什么意思?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆