Core Data 中线程安全的唯一实体实例

Posted

技术标签:

【中文标题】Core Data 中线程安全的唯一实体实例【英文标题】:Thread-safe unique entity instance in Core Data 【发布时间】:2010-02-02 05:07:16 【问题描述】:

我有一个具有 messageID 属性的 Message 实体。我想确保只有一个具有给定 messageID 的 Message 实体实例。在 SQL 中,我只需向 messageID 列添加一个唯一约束,但我不知道如何使用 Core Data 执行此操作。我不相信它可以在数据模型本身中完成,那你怎么做呢?

我最初的想法是使用验证方法来获取 NSManagedObject 的 ID 上下文,看看它是否找到除自身之外的任何内容,如果是,则验证失败。我怀疑这会起作用 - 但我担心这样的事情的表现。我付出了很多努力来最小化整个导入例程所需的获取请求,并且通过对每个新消息实体执行获取来验证它似乎有点过分。在执行将所有内容导入和连接在一起的实际工作之前,我可以使用两个获取查询来获取我需要的所有预先存在的对象并识别我需要插入到存储中的所有新对象。除了这两个之外,这将为每个更新或插入添加一个提取 - 这似乎消除了我首先通过预处理导入数据而获得的任何性能优势!

这是一个问题的主要原因是导入器可以(可能)在多个线程上同时运行多个批次,并且可能包含一些重叠/重复的数据,这些数据最终需要在存储中产生一个对象而不是重复条目。有没有一种合理的方法可以做到这一点,并且我所要求的对 Core Data 有意义吗?

【问题讨论】:

【参考方案1】:

保证唯一性的唯一方法是进行提取。幸运的是,您只需执行 -countForFetchRequest:error: 并检查它是否为零。这是目前保证唯一性的最便宜的方式。

您可能可以在验证中完成此操作,或者在处理数据的循环中运行它。就我个人而言,我会在 NSManagedObject 的创建之上执行此操作,这样当记录已经存在时,您就没有不必要的分配。

【讨论】:

获取是否安全?我如何确定在获取和创建新对象之间没有保存另一个线程的上下文? 获取是安全的,因为每个线程都有自己的上下文,它们会锁定底层的 NSPersistentStoreCoordinator,这样就不会发生冲突。 在完成获取和创建新对象并将其放入上下文之间是否存在时间间隔?我的意思是,当第一个线程完成并开始创建一个新对象时,另一个线程(具有自己的上下文)是否一直在等待进行提取?在第一个线程的 fetch 完成后,store 的锁被释放,因此第二个线程开始 fetch 并且还发现它需要创建对象。最终,这两个上下文都平安地保存到了商店中。现在商店中有两个具有相同 ID 的对象,对吧? 我认为 Marcus 在这里所说的是,您可以在保存期间调用验证例程期间以线程安全的方式进行提取,因为上下文锁定了持久存储协调器在保存期间。 [注意:我不是自己断言,我不确定。但从他所说的以及我对核心数据的了解来看,这似乎很可能。] 锁定持久存储协调器是允许核心数据中的原子“事务”的原因。 参见 Apple 的核心数据编程指南 / 核心数据的多线程 / 通用指南 / #1 “如果你想在一个上下文中聚合多个操作,就像一个虚拟的单个事务一样,你可以锁定持久存储协调器以防止其他托管对象上下文在多个操作的范围内使用持久存储协调器。”【参考方案2】:

我认为没有一种方法可以轻松保证属性的唯一性,而无需您自己进行大量工作。当然,您可以使用CFUUIDCreate 创建一个全局唯一的 UUID,它应该是唯一的,即使在多线程环境中也是如此。但是……

保证所有托管对象的objectID(类型NSManagedObjectID)在持久存储协调器中是唯一的。由于您可以向协调器添加任意多个持久存储,因此该保证基本上保证了objectIDs 是全局唯一的。为什么不使用objectID 作为你的messageID?当然,您不能在分配 objectID 后更改它(在保存包含插入对象的上下文之前,它不会被分配;在此之前它将是一个临时但仍然是唯一的 ID)。

【讨论】:

messageID 由服务器分配给消息。输入数据包括消息之间的关系,并通过 messageID 列表传输。我根据需要使用该信息来查找或创建消息对象,以便可以将它们直接相互连接。有时我从服务器以多种方式/上下文获得相同的消息,所以我需要确保我没有相同的唯一消息的重复实例,否则我会得到相互连接的消息对象的“云”。 (希望这是有道理的......)我认为在这种情况下我真的需要使用服务器的 messageID 作为“一个真实的 ID”。 ://【参考方案3】:

所以你为每个线程都有一个 NSManagedContext,由同一个持久存储支持,对吗?在保存 NSManagedContext 之前,您需要确保 messageID 是唯一的,也就是说,您没有更新现有行,并且它不在其他上下文中,对吗?

鉴于该模型(如果我误解了请纠正我),我认为拥有一个管理对持久存储的访问的对象会更好。这样,所有线程都会更新一个上下文,您可以使用 Marcus 的-countForFetchRequest:error: 建议在其中进行验证。诚然,这给这个操作带来了瓶颈。

【讨论】:

是的,这是一个正确的描述。我担心可能是这种情况,我最终不得不将所有内容限制在串行队列中,基本上。嘘。 :( 好吧,仅仅因为我会这样做并不意味着更聪明的人不会出现并提供更好的选择。我希望他们这样做。 你不应该有多个线程访问同一个上下文。这是失败的秘诀。每个线程都需要有自己的上下文访问相同的 NSPersistentStoreCoordinator 并让 Core Data 进行锁定。相信我,他们做对了,如果您遵循他们的线程规则,您可以相信不会发生冲突。 我相信你他们做对了。鉴于他正在进行的验证,我认为“串行队列”模型会更好,但我尊重您的专业知识。【参考方案4】:

只是为了增加我的 2 美分:我认为不一致迟早会发生,缓解它们的唯一方法似乎是在具有相当复杂代码的应用程序级别上进行。

所以在我的情况下,我决定允许应该是 "unique" 字段的重复值。

不过,我添加了代码,稍后会检测到这些问题(例如,当应该返回 1 个对象的提取返回超过 1 个时)并在它们发生时修复它们(通常通过删除)。

这是一个“去吧,犯了一个错误,以后会为你改正它”的策略

当然,这并不理想,但这是攻击此问题的有效方法,恕我直言。

【讨论】:

以上是关于Core Data 中线程安全的唯一实体实例的主要内容,如果未能解决你的问题,请参考以下文章

什么是线程安全,实现线程安全都有哪些方法

Core Data 3 托管对象上下文

java多线程之线程安全

线程安全的单实例模式

Core Data 实体在方法中返回后缺少其属性

Core Data 获取仅具有唯一属性的实体