【问题标题】:Core Data: Merging children added on a background context is funky核心数据:合并添加到背景上下文中的孩子很时髦
【发布时间】:2010-11-12 19:23:32
【问题描述】:

背景

  • 多线程核心数据应用程序

  • NSTreeControllerNSOutlineView 绑定

  • 在后台上下文的 NSOperation 中创建子对象

  • 使用mergeChangesFromContextDidSaveNotification合并到主上下文中

问题

  • 如果我将 20 个子对象创建操作排队,一旦合并完成,我在大纲视图中只看到大约 10-15 个子对象。

  • 如果我将最大并发操作数设置为 1,它会完美运行,我会看到 20 个孩子。

问题

我正在尝试做的事情是不可能的吗?我可以看到核心数据可能如何难以成功地进行合并。还是我的代码有问题?

代码

JGGroupController

 -(id)init {
     self = [super init];
     queue = [[NSOperationQueue alloc] init];
     [queue setMaxConcurrentOperationCount:10]; // If this is 1, it works like a dream. Anything higher and it bombs.
     return self;
 }

 -(IBAction)addTrainingEntryChild:(id)sender {
     moc  = [[NSApp delegate] managedObjectContext];
     JGTrainingBase *groupToAddTo = [[tree selectedObjects] objectAtIndex:0];
     for (NSUInteger i = 0; i < 20; i++) {
         JGAddChildrenObjectOperation    *addOperation = [[JGAddChildrenObjectOperation alloc] init]; 
         [addOperation addChildObjectToGroup:[groupToAddTo objectID]];
         [queue addOperation:addOperation];
     }
 }

JGAddChildrenObjectOperation - NSOperation 子类

 -(id)addChildObjectToGroup:(NSManagedObjectID *)groupToAddToID_ {
     groupToAddToObjectID = groupToAddToID_;
     return self;
 }

 -(void)main {
     [self startOperation];
     JGTrainingBase *groupToAddTo    = (JGTrainingBase *)[imoc objectWithID:groupToAddToObjectID];
     JGTrainingBase *entryChildToAdd = [JGTrainingBase insertInManagedObjectContext:imoc];
     [groupToAddTo addChildren:[NSSet setWithObject:entryChildToAdd]];
     [imoc save];
 [self cleanup];
     [self finishOperation];
 }

 -(void)mergeChanges:(NSNotification *)notification {
     NSManagedObjectContext *mainContext = [[NSApp delegate] managedObjectContext];
     [mainContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
                                   withObject:notification
                                waitUntilDone:YES];  
 }


 -(void)startOperation {
            // Omitted - Manage isExecuting, isPaused, isFinished etc flags

     imoc = [[NSManagedObjectContext alloc] init];
     [imoc setPersistentStoreCoordinator:[[NSApp delegate] persistentStoreCoordinator]];
     [imoc setUndoManager:nil];
     [imoc setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
     [imoc setStalenessInterval:0];

     [[NSNotificationCenter defaultCenter] addObserver:self
                                              selector:@selector(mergeChanges:) 
                                                  name:NSManagedObjectContextDidSaveNotification 
                                                object:imoc];
 }

 -(void)finishOperation {
            // Omitted - Manage isExecuting, isPaused, isFinished etc flags
 }

【问题讨论】:

    标签: objective-c multithreading cocoa core-data nsoperation


    【解决方案1】:

    您的操作使用商店中实体的不同“版本”。考虑以下操作顺序:

    您创建了 2 个操作,我们称它们为 O:F 和 O:G,它们将子 F 和 G 添加到组 1,记为 G:1 和子条目集 [A,B,C,D,E ]。

    操作队列同时出列 O:F 和 O:G,因此它们都获取托管对象上下文和实体 G:1。

    O:F 将 G:1 的子代设置为 [A,B,C,D,E,F]。 O:G 将 G:2 的子代设置为 [A,B,C,D,E,G]。

    无论哪个操作获胜,您最终都会得到 [A,B,C,D,E,F] 或 [A,B,C,D,E,G],两者都是商店中的值不正确。

    我相信 CoreData 应该在其中一个线程中引发乐观锁定错误,因为它的更改会过时。但我可能是错的。

    最重要的是,您正在跨线程改变同一个对象,而不同步对象的状态。不是创建 20 个操作,而是创建 1 个添加 20 个对象的操作,但是您有一个核心架构问题,即尝试在不同步的情况下从多个线程中改变同一个对象。

    每次都会失败。

    【讨论】:

    • 感谢另一个出色且非常明确的答案。我怀疑它会是这样的,但正如你所建议的,我希望 Core Data 抛出一个乐观锁定异常。现在您已经解释了发生的事情,这似乎很明显(再次)。底线似乎是这是不可能的。我假设 @synchronised 没有帮助,因为托管对象与另一个线程中的同一对象不在同一个地址 - 它们都在不同的上下文中。
    • 是的,您必须设置某种全局 NSLock 或 pthread 锁来同步线程。您可以想象的这种锁定方案变得非常棘手。我认为你应该注意的一件事是在这种情况下使用线程。除非您的对象具有非常昂贵的实例化成本,否则这种优化可能为时过早,并且您可以通过将 grunt 工作与主线程隔离来省去很多麻烦。
    • 这是在一个实验项目中探索如何合并瞬态属性,但在我的主项目中,我花了很多时间将操作转移到后台线程中。我希望在将内容移动到后台线程之前,我已经听取了 Matt Gallagher 的建议,以优化我的主线程方法的所有内容。我认为所有这些努力可能都白费了。
    猜你喜欢
    • 2017-08-21
    • 2021-04-11
    • 1970-01-01
    • 2019-07-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    相关资源
    最近更新 更多