Замыкание Core Data при выполнении запросов на выборку внутри блоков executeBlockAndWait

У меня возникают проблемы с Core Data, которые я не могу решить. Я узнал о проблемах concurrency в Core Data сложным образом, поэтому я очень осторожен и выполняю только операции с основными данными в блоках performBlock: и performBlockAndWait:.

Вот мой код:

/// Executes a fetch request with given parameters in context 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.

Я не могу сказать, почему я блокирую приложение и что мне делать по-другому. Мои исследования Qaru ничего мне не дали, поскольку большинство людей фиксировали все блокировки, отправляя запросы на выборку в очередь MOC, что я уже делаю.

Буду признателен за любую информацию по этому вопросу. Не стесняйтесь предоставлять ссылки для документации и другие длительные чтения: я очень хочу узнать больше о всех типах проблем и стратегий concurrency.

Ответ 1

Что интересно: кажется, что оба потока не могут получить некоторую блокировку в координаторе постоянных хранилищ, чтобы выполнить запрос на выборку.

Постоянный координатор хранилища является последовательной очередью. Если один контекст обращается к нему, будет заблокирован другой контекст.

От Документы Apple:

Координаторы делают все возможное, чтобы обеспечить concurrency - их операции сериализации. Если вы хотите использовать несколько потоков для разных операций записи, вы используете несколько координаторов. Обратите внимание: если несколько потоков работают напрямую с координатором, им необходимо заблокировать и разблокировать его явно.

Если вам нужно выполнить несколько запросов на выборку фона одновременно, вам понадобятся несколько постоянных координаторов хранилищ.

Несколько координаторов сделают ваш код немного более сложным, но его следует избегать, если это возможно. Вам действительно нужно делать несколько снимков одновременно? Не могли бы вы сделать более крупную выборку, а затем отфильтровать результаты в памяти?

Ответ 2

Если вам интересно узнать больше о Core Data (и потоке), следующий сайт будет чрезвычайно полезен. Я посетил Мэтью Мори в Атланте CocoaConf 2013.

Высокопроизводительные базовые данные (http://highperformancecoredata.com)

Код сопутствующего примера на веб-сайт можно найти по адресу: https://github.com/mmorey/MDMHPCoreData

Все, что вам нужно сделать в вашем приложении, - это иметь экземпляр Singleton (где-то) класса MDMPersistenceStack.

Насколько ваша проблема/проблема, даже если документация Apple для класса NSManagedObjectContext, позволяет писать код так, как у вас (с операциями Core Data, выполняемыми на экземпляре NSManagedObjectContext в блоке) и некоторым что я хотел бы указать, что это не единственный способ.

Всякий раз, когда у меня есть фиксированные приложения, у которых есть проблемы с Core Data concurrency (блокировка), самая простая вещь, на мой взгляд, заключается в создании частного NSManagedObjectContext внутри потока или блока, который я хочу выполнить с операциями Core Data.

Это может быть не самый элегантный подход, но он меня никогда не подводил. Всегда гарантируется, что 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 assume that this class method is in MyClass
         Remove the block that you have in your method, as it not needed 
      */
      [MyClass executeFetchRequestWithEntityName: ......]   // rest of parameters
});

Ответ 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;
}

}