gpt4 book ai didi

ios - xCode 7.0 IOS9 SDK : deadlock while executing fetch request with performBlockAndWait

转载 作者:行者123 更新时间:2023-12-01 16:49:19 25 4
gpt4 key购买 nike

更新:我已经准备好了可以无问题地再现问题的示例,请使用以下URL下载测试项目:
https://www.dsr-company.com/fm.php?Download=1&FileToDL=DeadLockTest_CoreDataWithoutMR.zip

提供的项目存在以下问题:提取时发生死锁
在performBlockAndWait中从主线程调用。

如果使用XCode版本> 6.4编译代码,则会重现此问题。
如果使用xCode == 6.4编译代码,则不会重现此问题。

老问题是:

我正在为IOS移动应用程序提供支持。
在最近将Xcode IDE从6.4版更新到7.0版(具有IOS 9支持)之后,我遇到了关键问题-应用程序挂起。
与xCode 6.4相同的应用程序构建(由相同的资源生成)可以正常工作。
因此,如果使用xCode> 6.4构建应用程序,则在某些情况下应用程序会挂起。
如果应用程序是使用xCode 6.4构建的,则该应用程序可以正常工作。

我花了一些时间研究问题,结果,我准备了具有类似情况的测试应用程序,就像在重现问题的应用程序中一样。
测试应用程序在Xcode> = 7.0上挂断,但在Xcode 6.4上正常运行

测试源下载链接:
https://www.sendspace.com/file/r07cln

测试应用程序的要求是:
1.必须在系统中安装 cocoa pod 管理器
2. 2.2版的MagicalRecord框架。

测试应用程序以以下方式工作:
1.在应用程序启动时,它将创建具有10000条简单实体记录的测试数据库,并将其保存到持久性存储中。
2.在应用程序的第一个屏幕上的viewWillAppear方法中:它运行导致死锁的测试。
使用以下算法:

-(NSArray *) entityWithId: (int) entityId inContext:(NSManagedObjectContext *)localContext 
{
NSArray * results = [TestEntity MR_findByAttribute:@"id" withValue:[ NSNumber numberWithInt: entityId ] inContext:localContext];
return results;
}

…..
int entityId = 88;
NSManagedObjectContext *childContext1 = [NSManagedObjectContext MR_context];
childContext1.name = @"childContext1";

NSManagedObjectContext *childContext2 = [NSManagedObjectContext MR_context];
childContext2.name = @"childContext2";

NSArray *results = [self entityWithId:entityId inContext: childContext2];

for(TestEntity *d in results)
{
NSLog(@"e from fetchRequest %@ with name = '%@'", d, d.name); /// this line is the reason of the hangup
}

dispatch_async(dispatch_get_main_queue(), ^
{
int entityId2 = 11;
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:@"id=%d", entityId2];
NSArray *a = [ TestEntity MR_findAllWithPredicate: predicate2 inContext: childContext2];
for(TestEntity *d in a)
{
NSLog(@"e from fetchRequest %@ with name = '%@'", d, d.name);
}
});

使用并发类型== NSPrivateQueueConcurrencyType创建两个受管对象上下文(请检查魔术记录框架的MR_context代码)。这两个上下文都有父上下文
并发类型= NSMainQueueConcurrencyType。从主线程应用程序以同步方式执行获取(MR_findByAttribute和MR_findAllWithPredicate
一起用于PerformBlockAndWait和)中的提取请求。在第一次获取之后,第二次获取是使用dispatch_async()在主线程上进行调度的。

结果,应用程序挂断了。似乎发生了死锁,请检查堆栈的屏幕截图:

is这里是链接,我的声誉太低,无法发布图像。 https://cdn.img42.com/34a8869bd8a5587222f9903e50b762f9.png)

如果要评论这一行
NSLog(@“fetchRequest%@中的e,名称='%@'”,d,d.name);///这是挂断的原因

(这是测试项目的ViewController.m中的第39行)应用程序可以正常运行。我相信这是因为没有读取测试实体的名称字段。

所以用注释行
NSLog(@“fetchRequest%@中的e,名称='%@'”,d,d.name);
Xcode 6.4和Xcode 7.0生成的二进制文件都没有挂断。

与未注释的行
NSLog(@“fetchRequest%@中的e,名称='%@'”,d,d.name);

Xcode 7.0构建的二进制文件没有挂断,而Xcode 6.4构建的二进制文件没有挂断。

我认为问题是由于实体数据的延迟加载而发生的。

所描述的情况有任何问题吗?我将不胜感激。

最佳答案

这就是为什么我不使用抽象(即隐藏)太多核心数据细节的框架的原因。它具有非常复杂的使用模式,有时您需要了解它们如何互操作的详细信息。

首先,我对魔术唱片一无所知,只是很多人都在使用它,因此它必须非常擅长它的工作。

但是,在您的示例中,我立即看到核心数据并发的几种完全错误的用法,因此我去看了看头文件,以了解为什么您的代码做出了假设。

我根本不是要打你,尽管乍一看似乎有点像。我想帮助您进行教育(我以此为契机来窥视MR)。

快速浏览MR,我想说您对MR的功能以及核心数据的一般并发规则有一些误解。

首先,你这样说...

Two managed object contexts are created with concurrency type == NSPrivateQueueConcurrencyType (please check the code of MR_context of magical record framework). Both contexts has parent context with concurrency type = NSMainQueueConcurrencyType.



这似乎是不正确的。这两个新上下文确实是私有(private)队列上下文,但是它们的父级(根据我在github上看过的代码)是神奇的 MR_rootSavingContext,它本身也是私有(private)队列上下文。

让我们分解一下您的代码示例。
NSManagedObjectContext *childContext1 = [NSManagedObjectContext MR_context];
childContext1.name = @"childContext1";

NSManagedObjectContext *childContext2 = [NSManagedObjectContext MR_context];
childContext2.name = @"childContext2";

因此,您现在有两个私有(private)队列MOC( childContext1childContext2),它们都是另一个匿名私有(private)队列MOC的子代(我们将称为 savingContext)。
NSArray *results = [self entityWithId:entityId inContext: childContext2];

然后,您对 childContext1执行获取。该代码实际上是...
-(NSArray *) entityWithId:(int)entityId
inContext:(NSManagedObjectContext *)localContext
{
NSArray * results = [TestEntity MR_findByAttribute:@"id"
withValue:[NSNumber numberWithInt:entityId]
inContext:localContext];
return results;
}

现在,我们知道,在这种情况下,此方法中的 localContext是指向 childContext2的另一个指针,它是私有(private)队列MOC。在对 performBlock的调用之外访问私有(private)队列MOC是违反并发规则的100%。但是,由于您正在使用另一个API,并且方法名称无法帮助您了解如何访问MOC,因此我们需要查看该API并查看它是否隐藏了 performBlock,以查看您是否正确访问了它。

不幸的是,头文件中的文档没有提供任何指示,因此我们必须看一下实现。该调用最终会调用 MR_executeFetchRequest...,后者在文档中也未指示它如何处理并发。因此,我们来看一下它的实现。

现在,我们到了某个地方。此函数确实尝试安全地访问MOC,但是它使用 performBlockAndWait,它将在调用时阻塞。

这是一条非常重要的信息,因为从错误的位置调用此信息确实会导致死锁。因此,您必须敏锐地意识到,只要执行获取请求,就会调用 performBlockAndWait。我自己的个人规则是 ,除非绝对没有其他选择,否则切勿使用 performBlockAndWait

但是,此调用应该是完全安全的...假设未在父MOC的上下文中调用它。

因此,让我们看下一段代码。
for(TestEntity *d in results)
{
NSLog(@"e from fetchRequest %@ with name = '%@'", d, d.name); /// this line is the reason of the hangup
}

现在,这不是MagicalRecord的错,因为在这里甚至没有直接使用MR。但是,您已经受过使用这些 MR_方法的培训,这些方法不需要并发模型的知识,因此您会忘记或永远不会学习并发规则。
results数组中的对象都是生活在 childContext2私有(private)队列上下文中的所有托管对象。因此,如果您不对并发规则表示敬意,则可能永远无法访问它们。这显然违反了并发规则。在开发应用程序时,应使用参数-com.apple.CoreData.ConcurrencyDebug 1启用并发调试。

此代码段必须包装在 performBlockperformBlockAndWait中。我几乎没有用过 performBlockAndWait,因为它有很多缺点-死锁就是其中之一。实际上,仅看到 performBlockAndWait的使用就非常有力地表明您的死锁正在那里发生,而不是在您所指示的代码行上发生。但是,在这种情况下,它至少与先前的提取一样安全,因此让我们使其更加安全...
[childContext2 performBlockAndWait:^{
for (TestEntity *d in results) {
NSLog(@"e from fetchRequest %@ with name = '%@'", d, d.name);
}
}];

接下来,您调度到主线程。是因为您只希望在随后的事件循环周期中发生某些事情,还是因为此代码已在其他线程上运行?谁知道。但是,您在这里遇到了同样的问题(为了便于阅读,我将代码重新格式化为帖子)。
dispatch_async(dispatch_get_main_queue(), ^{
int entityId2 = 11;
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:@"id=%d", entityId2];
NSArray *a = [TestEntity MR_findAllWithPredicate:predicate2
inContext:childContext2];
for (TestEntity *d in a) {
NSLog(@"e from fetchRequest %@ with name = '%@'", d, d.name);
}
});

现在,我们知道代码开始在主线程上运行,并且搜索将调用 performBlockAndWait,但是您随后在for循环中进行的访问再次违反了核心数据并发规则。

基于此,我看到的唯一真正的问题是...
  • MR似乎尊重其API中的核心数据并发规则,但是在访问托管对象时,您仍然必须遵循核心数据并发规则。
  • 我真的不喜欢performBlockAndWait的使用,因为这只是一个等待发生的问题。

  • 现在,让我们看一下挂起的屏幕截图。嗯...这是一个经典的死锁,但是没有意义,因为死锁发生在主线程和MOC线程之间。仅当主队列MOC是此私有(private)队列MOC的父代时,这种情况才会发生,但是代码显示并非如此。

    嗯...没有意义,所以我下载了您的项目,并查看了您上载的pod中的源代码。现在,该版本的代码将 MR_defaultContext用作使用 MR_context创建的所有MOC的父级。因此,默认的MOC确实是主队列MOC,现在这一切都变得很合理了。

    您有一个MOC作为主队列MOC的子代。当您将该块分派(dispatch)到主队列时,它现在正在作为主队列上的块运行。然后,该代码在该队列的MOC的子级上下文中调用 performBlockAndWait,这是一个很大的禁止,几乎可以保证您会遇到死锁。

    因此,似乎MR从那时起已将其代码从使用主队列作为新上下文的父级更改为使用私有(private)队列作为新上下文的父级(很可能是由于这个确切的问题)。因此,如果您升级到最新版本的MR,则应该可以。

    但是,我仍然警告您,如果您想以多线程方式使用MR,则必须确切了解它们如何处理并发规则,并且还必须确保在访问任何非核心数据对象时都遵守它们通过MR API。

    最后,我只是说我已经完成了无数的核心数据工作,而且我从未使用过试图向我隐藏并发问题的API。原因是有太多的小问题,我宁愿以务实的方式处理它们。

    最后,除非您确切知道为什么它是唯一的选择,否则您几乎应该永远不要使用 performBlockAndWait。至少在我看来,将其用作您下面的API的一部分更加可怕。

    我希望这件事能给您(以及其他一些人)带来启发和帮助。当然,这为我带来了一点启发,并帮助我重新建立了一些以前毫无根据的轻率。

    编辑

    这是对您提供的“非魔术记录”示例的响应。

    相对于MR发生的事情,此代码的问题与我上面描述的完全相同。

    您有一个私有(private)队列上下文,作为主队列上下文的子级。

    您正在主队列上运行代码,并在子上下文上调用 performBlockAndWait,然后在尝试执行提取时必须锁定其父上下文。

    它被称为死锁,但更具描述性(和诱人)的术语是致命的拥抱。

    原始代码在主线程上运行。它调用子级上下文以执行某项操作,并且在该子级完成之前不执行任何其他操作。

    然后,那个 child 为了完成任务,需要主线程来做某事。但是,主线程在子进程完成之前无法执行任何操作……但是子进程正在等待主线程执行某项操作……

    任何人都无法取得进展。

    您面临的问题已得到很好的记录,实际上,在WWDC演示文稿和多篇文档中已多次提及。

    您应该在子上下文上 永远不要调用 performBlockAndWait

    过去您摆脱它的事实只是“偶然”,因为它根本不应该以这种方式工作。

    实际上,几乎不应该每次调用 performBlockAndWait

    您应该真正习惯于进行异步编程。这是我建议您重写此测试的方式,无论哪种方式提示了此问题。

    首先,重写提取,使其异步运行...
    - (void)executeFetchRequest:(NSFetchRequest *)request
    inContext:(NSManagedObjectContext *)context
    completion:(void(^)(NSArray *results, NSError *error))completion
    {
    [context performBlock:^{
    NSError *error = nil;
    NSArray *results = [context executeFetchRequest:request error:&error];
    if (completion) {
    completion(results, error);
    }
    }];
    }

    然后,您更改您的代码,该代码调用fetch来执行以下操作...
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    [request setEntity: testEntityDescription ];
    [request setPredicate: predicate2 ];
    [self executeFetchRequest:request
    inContext:childContext2
    completion:^(NSArray *results, NSError *error) {
    if (results) {
    for (TestEntity *d in results) {
    NSLog(@"++++++++++ e from fetchRequest %@ with name = '%@'", d, d.name);
    }
    } else {
    NSLog(@"Handle this error: %@", error);
    }
    }];

    关于ios - xCode 7.0 IOS9 SDK : deadlock while executing fetch request with performBlockAndWait,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32887919/

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