在performBlockAndWait块中执行获取请求时,核心数据死锁

时间:2014-02-26 12:38:59

标签: ios multithreading core-data concurrency deadlock

我遇到了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];

这很有趣:似乎两个线程都无法在持久存储协调器上获取某些锁定以执行获取请求。

我的所有背景都是NSPrivateQueueConcurrencyType

我无法指责,我为什么要锁定应用程序以及我应该采取哪些不同的方式。我对Stack Overflow的研究没有给我什么,因为大多数人通过在MOC的队列上调度获取请求来修复所有锁,我已经这样做了。

我将很感激有关此问题的任何信息。随意提供文档链接和其他长读:我渴望了解更多关于所有类型的并发问题和策略。

3 个答案:

答案 0 :(得分:2)

  

有趣的是:似乎两个线程都无法在持久性存储协调器上获取某些锁定以执行获取请求。

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

来自Apple Docs

  

协调员与提供并发相反 - 他们将操作序列化。如果要将多个线程用于不同的写操作,请使用多个协调器。请注意,如果多个线程直接与协调器一起工作,则需要明确锁定和解锁它。

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

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

答案 1 :(得分:0)

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

高效核心数据(http://highperformancecoredata.com

网站的随附示例代码位于:https://github.com/mmorey/MDMHPCoreData

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

就您的问题/问题而言,尽管NSManagedObjectContext类的Apple文档确实允许以您拥有的方式编写代码(在块中的NSManagedObjectContext实例上执行Core Data操作)和某些代码范围鼓励 - 我想指出这不是唯一的方法。

每当我修复了具有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
});

答案 2 :(得分:-1)

我是这样做的。它解决了我的问题。我也经历了很多僵局。看看它是否适合你。

    + (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;
}

}