Проблема с синхронизацией NSOperationQueue, CoreData и MagicalRecord

Я создаю NSOperationQueue для всех операций, касающихся inserting/updating/deleting/searching одной единственной таблицы. Я использую MagicalRecord для работы с данными Core. Но у меня проблема с синхронизацией. Упрощенный пример выглядит следующим образом.

Например. Таблица с именем person и столбец внутри человека с именем like. Когда пользователь нажимает кнопку, like увеличивается на единицу. Я делаю это как

[SameBackgroundQueue addOperationWithBlock:^{
    User *user = [User MR_findFirstWithPredicate:some_predicate];
    user.like += 1;
    NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];
    [localContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
        [SameBackgroundQueue addOperationWithBlock:^{
            [self syncWithServer];//This can take time
    }];
}];

Однако, если пользователь щелкает быстро, подобное не будет правильным, потому что MR_findFirstWithPredicate может получить грязную запись. Проблема возникает из-за того, что NSOperationQueue может задействовать разные потоки, а MR_findFirstWithPredicate использует контекст из текущего потока. Таким образом, потенциально он попытается получить пользователя из другого NSManagedObjectContext и, следовательно, получит грязные данные.

Конечно, если мы будем использовать mainQueue, у нас не будет никаких проблем. Однако как я могу использовать фоновый поток, а также убедиться, что у меня нет проблем с грязной записью. Кажется, вся проблема может быть решена, если я буду работать с одним конкретным потоком, а не с пользователем NSOperationQueue. Должен ли я использовать вместо этого GCD?


В моем предыдущем проекте я использую

[self createManagedObjectContextWithParent:self.mainQueueManagedObjectContext andConcurrencyType:NSPrivateQueueConcurrencyType];

и

[managedObjectContext save:&error];
if (managedObjectContext.parentContext) {
    [managedObjectContext.parentContext performBlock:^{
        NSError *parentError = nil;
        [managedObjectContext.parentContext save:&parentError];
    }];
}

Я знаю, что есть видеоролики WWDC, в которых говорится о NSManagedObjectContext, параллельном контексте и т. Д. Но если я использую это, я не могу использовать MagicRecord.

Любое предложение будет высоко оценено.


На самом деле я нахожу более эффективный способ сделать это. Пожалуйста, поправьте меня, если я делаю что-то не так.

Фактически я создаю общий одноэлементный контекст [NSManagedObjectContext MR_context], который будет использоваться для User OperationQueue. Таким образом, даже все потоки обращаются к одному и тому же контексту, они не получат грязные данные.

По-прежнему будут сбои, например, что, если два потока изменяют один и тот же объект одного и того же контекста. Обычно это очень редкий случай, но я посмотрю, как все пойдет. Я могу установить максимальное количество одновременных потоков равным единице, просто чтобы избежать ситуации. Я не уверен, снизит ли это производительность. Буду обновлять прогресс завтра.


person Ethan Fang    schedule 11.02.2014    source источник


Ответы (2)


Я бы порекомендовал изменить ваш рабочий блок на что-то вроде этого:

[TheSameQueue addOperationWithBlock:^{
    NSManagedObjectContext *localContext = [NSManagedObjectContext MR_context];

    User *user = [User MR_findFirstWithPredicate:some_predicate inContext:localContext];
    user.like += 1;

    [localContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
        [self syncWithServer];//This can take time
    }];
}];

Вы выполняли все фоновые операции в контексте по умолчанию, который можно рассматривать как контекст основного потока. Обычно это срабатывает в 99% случаев, но приводит к случайным тупикам и сбоям. Кроме того, MR_contextForCurrentThread устарел, потому что он также вызывает проблемы с очередями, а именно использование неправильного контекста для потока.

person casademora    schedule 11.02.2014
comment
Спасибо за Ваш ответ. Насколько я понимаю, MR_context - это еще один контекст, который является потомком MR_defaultContext, который по умолчанию является mainQueueContext. - person Ethan Fang; 11.02.2014
comment
Кстати, спасибо за ответ. - person Ethan Fang; 11.02.2014
comment
Так что технически MR_context - это фоновый контекст, а не контекст mainQueue :) - person Ethan Fang; 11.02.2014
comment
Правильно, исправление состоит в том, чтобы использовать фоновый контекст в фоновом потоке. MagicalRecord имеет для этого API, поэтому он может настроить слияния для вас, чтобы при сохранении основной контекст содержал все ваши изменения для вас. - person casademora; 11.02.2014

Чтобы избежать этого состояния гонки, вы можете ограничить свой NSOperationQueue одной операцией за раз, установив:

[TheSameQueue setMaxConcurrentOperationCount:1];

Отображение очереди последовательного выполнения.

Вы также можете создать последовательную очередь с помощью GCD (но тогда вам будет сложнее отменить и установить зависимости для операций).

(оба используют GCD в фоновом режиме)

Я бы выполнил синхронизацию сервера в другой очереди, чтобы она не мешала работе пользователя локально (вы также можете вносить локальные изменения в основной поток и синхронизировать только в фоновом режиме)

Ваша синхронизация с сервером может быть проблематичной, поскольку локальные изменения не отслеживаются, и необходимость синхронизации объекта с сервером не сохраняется между запусками приложения (сохранение для сохранения выполнено успешно, но синхронизация с сервером прервана / не удалась и т. Д.).

person Dan Shelly    schedule 11.02.2014
comment
Привет, это может быть решением. Но это ограничит производительность фоновой очереди. Спасибо за предупреждение о проблемах с синхронизацией. Я тоже стараюсь изо всех сил, например, отслеживаю статус, сравниваю измененное время и т. Д. - person Ethan Fang; 11.02.2014
comment
Прямо сейчас функция синхронизации пользователей также использует UserQueue. Возможно, мне следует использовать другую очередь UserSyncQueue, чтобы User CoreData использовал только один параллельный поток, но UserSyncQueue мог одновременно обрабатывать несколько потоков. - person Ethan Fang; 11.02.2014