核心数据:合并添加到背景上下文中的子项很时髦
背景
多线程核心数据应用程序
NSTreeController
和带有绑定的NSOutlineView
在后台上下文的 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
}
Background
Multi threaded Core Data application
NSTreeController
andNSOutlineView
with bindingsCreates children objects in an NSOperation on a background context
Merges into main context using
mergeChangesFromContextDidSaveNotification
Problem
If I queue 20 children creation operations up, once the merges have completed, I see only about 10-15 child objects in the outline view.
If I set the max concurrent operations to 1, it works perfectly and I see 20 children.
Question
Is what I'm trying to do impossible? I can see how core data might struggle to do the merges successfully. Or is there an issue with my code?
Code
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 subclass
-(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
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(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 个对象的操作,但您会遇到一个核心架构问题,即尝试在不同步的情况下从多个线程更改同一对象。
这样每次都会失败。
Your operations are using different "versions" of the entity from the store. Consider this order of operations:
You create 2 operations, let's call them O:F and O:G which are to add children F and G to group 1, noted as G:1 with a children entry set [A,B,C,D,E].
The operation queue dequeues O:F and O:G at the same time, thus they both fetch a managed object context and entity G:1.
O:F sets children of G:1 to [A,B,C,D,E,F].
O:G sets children of G:2 to [A,B,C,D,E,G].
It doesn't matter which operation wins, you will end up with either [A,B,C,D,E,F] or [A,B,C,D,E,G], both of which are incorrect values in the store.
I believe CoreData should be throwing an optimistic locking error in one of those threads though, as it's changes would be out of date. But I could be wrong.
The bottom line is you're mutating the same object across threads without synchronizing the state of the object. Instead of creating 20 operations create 1 operation which adds 20 objects, but you have a core architectural problem of trying to mutate the same object from multiple threads without synchronization.
That will fail every time.