【问题标题】:Core Data deadlocking when executing fetch requests inside performBlockAndWait blocks在 performBlockAndWait 块中执行 fetch 请求时核心数据死锁
【发布时间】:2014-03-29 07:08:45
【问题描述】:

我遇到了无法解决的 Core Data 问题。我已经通过艰难的方式了解了 Core Data 中的并发问题,因此我非常小心,只在 performBlock:performBlockAndWait: 块中执行任何核心数据操作。

这是我的代码:

/// Executes a fetch request with given parameters in context's block.
+ (NSArray *)executeFetchRequestWithEntityName:(NSString *)entityName
                                 predicate:(NSPredicate *)predicate
                                fetchLimit:(NSUInteger)fetchLimit
                            sortDescriptor:(NSSortDescriptor *)sortDescriptor
                                 inContext:(NSManagedObjectContext *)context{
    NSCAssert(entityName.length > 0,
          @"entityName parameter in executeFetchRequestWithEntityName:predicate:fetchLimit:sortDescriptor:inContext:\
          is invalid");

    __block NSArray * results = nil;

    NSPredicate * newPredicate = [CWFCoreDataUtilities currentUserPredicateInContext:context];
    if (predicate){
        newPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[newPredicate, predicate]];
    }

    [context performBlockAndWait:^{

        NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:entityName];
        request.fetchLimit = fetchLimit;
        request.predicate = newPredicate;
        if (sortDescriptor) {
            request.sortDescriptors = @[sortDescriptor];
        }

        NSError * error = nil;
        results = [context executeFetchRequest:request error:&error];

        if (error){
            @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                           reason:@"Fetch requests are required to succeed."    
                                         userInfo:@{@"error":error}];
             NSLog(@"ERROR! %@", error);
        }

        NSCAssert(results != nil, @"Fetch requests must succeed");
    }];

    return results;
}

当我从两个不同的线程同时进入此方法并传递两个不同的上下文时,我会导致此行出现死锁:results = [context executeFetchRequest:request error:&error];

有趣的是:似乎两个线程都无法在 Persistent Store Coordinator 上获取一些锁来执行获取请求。

我所有的上下文都是NSPrivateQueueConcurrencyType

我无法解决问题,为什么我要锁定应用程序以及我应该采取哪些不同的做法。我对 Stack Overflow 的研究没有给我任何帮助,因为大多数人通过在 MOC 的队列上分派获取请求来修复所有锁,而我已经这样做了。

我将不胜感激有关此问题的任何信息。随时提供文档链接和其他长篇文章:我渴望了解更多关于各种并发问题和策略的信息。

【问题讨论】:

  • 您能否提供代码:currentUserPredicateInContext:predicate 构造(它们是否使用来自其他上下文的对象来构造)?
  • 你确定这是死锁吗?我建议你发布线程的堆栈跟踪
  • @DanShelly 这个错误很难重现,我今天没有发现它,所以没有堆栈跟踪。谓词的格式为“owner = %@”,其中 %@ 是来自用户默认值的字符串。我根本没有在谓词中使用任何 MObject。我确信这是一个死锁,因为在堆栈跟踪中,我的两个线程卡在 mutex_wait 上,这是调用 executeFetchRequest 的结果。
  • 您使用类方法的任何原因。这可能是你的问题。
  • 我们遇到了这个问题,我们最终承认失败。我们将其更改为使用 performBlock: 并使用异步 API(完成块)。

标签: ios multithreading core-data concurrency deadlock


【解决方案1】:

有趣的是:似乎两个线程都无法在 Persistent Store Coordinator 上获取一些锁以执行获取请求。

持久存储协调器是一个串行队列。如果一个上下文正在访问它,另一个上下文将被阻止。

来自Apple Docs

协调器的作用与提供并发性相反——它们序列化操作。如果你想为不同的写操作使用多个线程,你可以使用多个协调器。请注意,如果多个线程直接与协调器一起工作,则它们需要显式地锁定和解锁它。

如果您需要同时执行多个后台获取请求,您将需要多个持久存储协调器。

多个协调器只会使您的代码稍微复杂一些,但应尽可能避免。您真的需要同时进行多次提取吗?你能做一个更大的提取然后过滤内存中的结果吗?

【讨论】:

    【解决方案2】:

    如果您有兴趣了解有关 Core Data(和线程)的更多信息,以下网站将非常有帮助。我参加了 Matthew Morey 在 2013 年亚特兰大 CocoaConf 的演讲。

    高性能核心数据 (http://highperformancecoredata.com)

    该网站的配套示例代码位于:https://github.com/mmorey/MDMHPCoreData

    您需要在您的应用程序中做的就是拥有一个 MDMPersistenceStack 类的 Singleton 实例(某处)。

    就您的问题/问题而言,即使 NSManagedObjectContext 类的 Apple 文档确实允许按照您的方式编写代码(在块中的 NSManagedObjectContext 实例上执行核心数据操作)和一些程度鼓励这一点 - 我想指出这不是唯一的方法。

    每当我修复了存在 Core Data 并发问题(锁定)的应用程序时,我认为最简单的方法是在我想要执行 Core Data 操作的线程或块内创建一个私有 NSManagedObjectContext。

    这可能不是最优雅的方法,但它从未让我失望。始终保证 NSManagedObjectContext 是在同一个线程中创建和执行的,因为 NSManagedObjectContext 是显式创建的。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(queue, ^{
          /* 
             Create NSManagedObjectContext
             Concurrency of Managed Object Context should be set to NSPrivateQueueConcurrencyType
             If you use the MDMPersistenceStack class this is handled for you.
          */
          NSManagedObjectContext *managedObjectContext = ....  
    
          /*
             Call the method that you have listed in your code.  
             Let's assume that this class method is in MyClass
             Remove the block that you have in your method, as it's not needed 
          */
          [MyClass executeFetchRequestWithEntityName: ......]   // rest of parameters
    });
    

    【讨论】:

    • 这是一个非常糟糕的答案。首先,如NSManagedObjectContext's class reference: Private queue (NSPrivateQueueConcurrencyType) 中所述——上下文创建并管理一个私有队列。 (...) 您可以结合使用基于队列的并发类型和 performBlock: 和 performBlockAndWait: 来使用上下文。您将“标准”消息分组以发送到块内的上下文以传递给这些方法之一。
    • 因此,我对这些块所做的是使用 NSPrivateQueueConcurrencyType 上下文处理并发的正确方法,而您的建议可能是使用 NSConfinementConcurrencyType 上下文的方式。
    • 我们将不得不同意不同意,因为我的回答很差。 :-) 也许你不喜欢我试图简化事情的部分。在 iOS 4 之前的世界中,这将是一个出色的答案。
    • 我将更新/编辑答案 - 使简单版本用于使用 MDMPersistenceStack 类。如果您使用该类,它将处理有关线程和核心数据的主要问题/问题
    • 您在这里的建议显然与文档背道而驰(请参阅第一条评论)。 NSPrivateQueueConcurrencyType 表示上下文具有 its own queue,您必须将其用于所有上下文操作。你告诉我我应该使用 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 提供的一些随机线程来代替上下文操作,即你建议确保在 wrong 线程上执行操作。如果您告诉我改用 NSConfinementConcurrencyType 上下文,您的答案就可以了,但我需要一个同步调用(请参阅问题)。
    【解决方案3】:

    我是这样做的。它为我解决了这个问题。我也遇到了很多死锁。看看它是否适合你。

        + (NSArray *)getRecordsForFetchRequest:(NSFetchRequest *)request inContext:(NSManagedObjectContext *)context
    {
    
    
    @try
    {
       __weak __block NSError *error = nil;
    
        __block __weak NSArray *results = nil;
    
    
        [context performBlockAndWait:^{
            [context lock];
    
            results = [context executeFetchRequest:request error:&error];
    
            [context processPendingChanges];
    
            [context unlock];
    
        }];
    
        [self handleErrors:error];
    
        request = nil;
        context = nil;
    
        return results;
    }
    @catch (NSException *exception)
    {
        if([exception.description rangeOfString:@"Can only use -performBlockAndWait: on an NSManagedObjectContext that was created with a queue"].location!=NSNotFound)
        {
            NSError *error = nil;
    
            [context lock];
    
           __weak NSArray *results = [context executeFetchRequest:request error:&error];
    
            [context processPendingChanges];
    
            [context unlock];
    
            [self handleErrors:error];
    
            request = nil;
            context = nil;
    
            return results;
        }
    
        return nil;
    }
    

    }

    【讨论】:

    • 永远不要在 Objective-C 的常规代码中使用异常处理。仅针对逻辑或内部错误引发异常,并且不应在常规现实生活中发生。具体来说,您应该在这里创建两种方法:一种为配置了私有队列的上下文获取结果,另一种为其他上下文获取结果。
    猜你喜欢
    • 2015-12-29
    • 2012-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-25
    • 1970-01-01
    • 1970-01-01
    • 2018-12-05
    相关资源
    最近更新 更多