gpt4 book ai didi

ios - ManagedObjectContext performBlock(AndWait)死锁

转载 作者:塔克拉玛干 更新时间:2023-11-02 10:05:42 27 4
gpt4 key购买 nike

我之前已经看过这个问题,但是在我的情况下,所有解决方案似乎都没有影响,这是:

我的应用程序使用三个ManagedObjectContexts:

1)在全局(后台)队列上创建具有NSPrivateQueueConcurrencyType并且没有父上下文的“diskManagedObjectContext”,并将其用于在后台线程上将上下文更改写入磁盘(持久存储):

-(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上,只需将更改“向上”推到其父磁盘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上,只需将更改“向上”推送到其父级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-(同步)-> mainMOC-(异步)-> diskMOC-(异步)->磁盘),作为我称之为“ManagedObjectLibrarian”类(有两种情况,一种是保存并封装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上,以便我可以根据需要从任何线程执行这些保存。)

我认为所有这些都是非常典型的,因此希望到目前为止一切都很好。

[序言完]

这是问题所在:我在启动时或启动时遇到明显的死锁
 [self.managedObjectContext performBlockAndWait:  *[SYNCHRONOUS]*

在此saveChanges方法的顶部。即使不是在performBlockAndWait:块中本身执行此方法的调用,也会发生这种情况—实际上,我遇到的第一个死锁是在应用启动时在主线程上调用了以下内容:
[self.backgroundManagedObjectLibrarian saveChanges:nil];

查看发生此死锁时的线程状态,另一个阻塞线程在对MOC的save:方法的调用中死锁在其上的performBlock中:
[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:,则不会发生死锁(嗯,直到执行到达下一个异步模型实体属性更改更改保存为止)。

我想当然可以让所有异步模型实体属性更改同步进行,但是我觉得我不必这样做,尤其是因为我不需要那些更改立即传播,也不要坚持要求任何回报或结果。

[问题]

所以,问题:

1)为什么我在这里陷入僵局?
2)我在做什么错和/或误解?
3)那么,管理嵌套的MOC保存和单个模型实体属性更改保存的正确模式是什么?

谢谢!

卡尔

最佳答案

您的实现存在许多问题。

  • 方法diskManagedObjectContextbackgroundManagedObjectContext
    使用全局并发队列,这没有任何意义。如果要使方法线程安全(考虑实例变量是共享资源),则需要使用专用队列(串行或并发队列),并使用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尽力避免这种情况。

    因此,尽可能地考虑异步-并且您不会遭受死锁。在您的方案中,采用非阻塞异步样式很容易。

    关于ios - ManagedObjectContext performBlock(AndWait)死锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22947026/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com