我之前已经看过这个问题,但是没有一个解决方案似乎对我的情况有任何影响,这就是:
我的应用使用三个ManagedObjectContexts:
1)在全局(后台)队列上创建" diskManagedObjectContext",带有NSPrivateQueueConcurrencyType且没有父上下文,用于在后台线程上将上下文更改写入磁盘(持久存储):
-(NSManagedObjectContext *)diskManagedObjectContext
{
if (! _diskManagedObjectContext)
{
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
_diskManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_diskManagedObjectContext.persistentStoreCoordinator = self.ircManagedObjectLibrary.persistentStoreCoordinator;
});
}
return _diskManagedObjectContext;
}
2)带有NSMainQueueConcurrencyType的" mainManagedObjectContext"将diskManagedObjectContext作为其父上下文,在主线程上创建(在应用程序启动时),并由所有GUI进程(视图控制器等)使用。 )。保存在mainManagedObjectContext上只需按下更改" up"到其父级diskManagedObjectContext,然后将异步写入磁盘。
-(NSManagedObjectContext *)mainManagedObjectContext
{
if (! _mainManagedObjectContext)
{
_mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_mainManagedObjectContext.undoManager = nil;
_mainManagedObjectContext.parentContext = self.diskManagedObjectContext;
}
return _mainManagedObjectContext;
}
3)带有NSPrivateQueueConcurrencyType的" backgroundManagedObjectContext"将mainManagedObjectContext作为其父上下文,在全局(后台)队列上创建,并由所有非GUI进程使用(数据存档,日志记录,等)在后台进行相对低优先级的模型更改。保存在backgroundManagedObjectContext上只需按下更改" up"在其父级mainManagedObjectContext中,此时监听与其相关的模型更改的GUI元素获取事件并相应地更新。
-(NSManagedObjectContext *)backgroundManagedObjectContext
{
if (! _backgroundManagedObjectContext)
{
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
_backgroundManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_backgroundManagedObjectContext.undoManager = nil;
_backgroundManagedObjectContext.parentContext = self.mainManagedObjectContext;
});
}
return _backgroundManagedObjectContext;
}
我已经实现了上面描述的嵌套保存行为(backgroundMOC - (sync) - > mainMOC - (async) - > diskMOC - (async) - > disk)因此,作为一种方法对我说了什么打电话给" ManagedObjectLibrarian" class(其中有两个实例,一个包含并封装mainMOC,另一个包含backgroundMOC):
-(BOOL)saveChanges:(NSError **)error
{
__block BOOL successful = NO;
*[DEADLOCKS HERE]* [self.managedObjectContext performBlockAndWait: *[SYNCHRONOUS]*
^{
NSError *internalError = nil;
// First save any changes on the managed object context of this ManagedObjectLibrarian. If the context has a parent, this does not write changes to disk but instead simply pushes the changes to the parent context in memory, and so is very fast.
successful = [_managedObjectContext save:&internalError];
if (successful)
{
// If successful, then if the context of this ManagedObjectLibrarian has a parent with private queue concurrency (which only the app delegate's mainIrcManagedObjectLibrarian does), save the changes on that parent context, which if it does not itself have a parent will write the changes to disk. Because this write is performed as a block operation on a private queue, it is executed in a non-main thread.
if (_managedObjectContext.parentContext)
{
[_managedObjectContext.parentContext performBlock: *[ASYNCHRONOUS]*
^{
if (_managedObjectContext.parentContext.concurrencyType == NSPrivateQueueConcurrencyType)
{
NSError *parentError = nil;
BOOL parentSuccessful = [_managedObjectContext.parentContext save:&parentError];
[Error handling, etc.]
这些嵌套保存中的第一个是在performBlockAndWait中执行的,原因如下:
1)它遵循MOC自己的save方法的模式,该方法返回BOOL成功结果(因此在初始保存完成或失败之前不得返回)。 2)由于mainMOC和backgroundMOC都有父上下文,因此它们的保存仅将更改推送到父项,因此非常快(因此不需要异步执行,与diskMOC保存不同)。
(如果它很重要:我执行后台ManagedObjectLibrarian和主ManagedObjectLibrarian保存在performBlock(AndWait)中的原因:首先在它们各自的MOC上,以便我可以根据需要从任何线程执行这些保存。)< / p>
我认为所有这些都是非常典型的,而且希望如此,这么好。
[序言结尾]
问题在于:我在
开始时遇到明显的僵局 [self.managedObjectContext performBlockAndWait: *[SYNCHRONOUS]*
位于此saveChanges方法的顶部。这种情况正在发生,即使对此方法的调用 not 本身也是在performBlockAndWait:block中执行的 - 事实上,我遇到的第一个死锁是在app启动时,在主线程上调用以下内容时:
[self.backgroundManagedObjectLibrarian saveChanges:nil];
查看线程状态,当发生此死锁时,另一个阻塞线程在调用MOC的save:方法时对其执行死锁:
[self.dataSourceProperties.managedObjectContext performBlock: *[ASYNCHRONOUS]*
^{
self.dataSourceProperties.dataArchiving.isEnabled = [NSNumber numberWithBool:_dataArchivingIsEnabled];
*[DEADLOCKS HERE]* [self.dataSourceProperties.managedObjectContext save:nil];
}];
这里保存的dataArchiving.isEnabled模型实体属性更改是在peformBlockAndWait中进行的:在同一个backgroundManagedObjectLibrarian上; 但上面调用saveChanges:在backgroundManagedObjectLibrarian上,在之外的以及之后的块返回。所以我不知道同步(peformBlockAndWait :)块中的早期异步(peformBlock :)保存是如何阻止同步块之外的后续同步(peformBlockAndWait :)保存。
还有什么,如果我将这个模型 - 实体 - 属性 - 更改保存操作从performBlock更改为performBlockAndWait:,则不会发生死锁(好吧,直到执行到达下一个异步模型实体属性) - 改变保存)。
我想我当然可以通过并使我的所有异步模型 - 实体 - 属性 - 更改保存同步,但我觉得我不应该这样做,特别是因为我没有&#39;这些变化需要立即传播,也不要留下任何回报或结果。
[问题]
所以,问题:
1)为什么我在这里遇到僵局? 2)我做错了什么和/或误会了? 3)管理嵌套MOC保存和单个模型 - 实体 - 属性 - 更改保存的正确模式是什么?
谢谢!
卡尔
答案 0 :(得分:3)
您的实施存在许多问题。
方法diskManagedObjectContext
和backgroundManagedObjectContext
使用全局并发队列,这没有任何意义。如果要使方法线程安全(考虑到实例变量是共享资源),则需要使用专用队列(串行或并发)并使用dispatch_barrier_sync
来返回一个值,dispatch_barrier_async
到写一个值。
您的saveChanges
方法应该是异步的
简单通用的实现可能如下所示:
- (void) saveContextChainWithContext:(NSManagedObjectContext*)context
completion:(void (^)(NSError*error))completion
{
[context performBlock:^{
NSError* error;
if ([context save:&error]) {
NSManagedObjectContext* ctx = [context parentContext];
if (ctx) {
dispatch_async(dispatch_get_global(0,0), ^{
[self saveContextChainWithContext:ctx completion:completion];
});
}
else {
if (completion) {
completion(nil);
}
}
}
else {
if (completion) {
completion(error);
}
}
}];
}
通常,使用方法的同步阻止版本总是容易出现死锁 - 即使Core Data努力尽力避免此类情况。
因此,尽可能考虑异步 - 并且不会遇到死锁。在您的场景中,使用非阻塞异步样式很容易。