Базовые данные: объединение дочерних элементов, добавленных в фоновый контекст, является прикольным - PullRequest
3 голосов
/ 12 ноября 2010

Фон

  • Многопоточное приложение Core Data

  • NSTreeController и NSOutlineView с привязками

  • Создает дочерние объекты в NSOperation на фоновом контексте

  • Объединяет в основной контекст, используя mergeChangesFromContextDidSaveNotification

Data Model

Проблема

  • Если я поставлю в очередь 20 дочерних операций по созданию, после завершения слияний я вижу только 10-15 дочерних объектов в виде структуры.

  • Если я установлю максимальное число одновременных операций на 1, оно будет отлично работать, и я увижу 20 детей.

Вопрос

Это то, что япытаюсь сделать невозможное?Я вижу, как основные данные могут изо всех сил пытаться сделать слияния успешно.Или есть проблема с моим кодом?

Код

JGGroupController

 -(id)init {
     self = [super init];
     queue = [[NSOperationQueue alloc] init];
     [queue setMaxConcurrentOperationCount:10]; // If this is 1, it works like a dream. Anything higher and it bombs.
     return self;
 }

 -(IBAction)addTrainingEntryChild:(id)sender {
     moc  = [[NSApp delegate] managedObjectContext];
     JGTrainingBase *groupToAddTo = [[tree selectedObjects] objectAtIndex:0];
     for (NSUInteger i = 0; i < 20; i++) {
         JGAddChildrenObjectOperation    *addOperation = [[JGAddChildrenObjectOperation alloc] init]; 
         [addOperation addChildObjectToGroup:[groupToAddTo objectID]];
         [queue addOperation:addOperation];
     }
 }

JGAddChildrenObjectOperation - подкласс NSOperation

 -(id)addChildObjectToGroup:(NSManagedObjectID *)groupToAddToID_ {
     groupToAddToObjectID = groupToAddToID_;
     return self;
 }

 -(void)main {
     [self startOperation];
     JGTrainingBase *groupToAddTo    = (JGTrainingBase *)[imoc objectWithID:groupToAddToObjectID];
     JGTrainingBase *entryChildToAdd = [JGTrainingBase insertInManagedObjectContext:imoc];
     [groupToAddTo addChildren:[NSSet setWithObject:entryChildToAdd]];
     [imoc save];
 [self cleanup];
     [self finishOperation];
 }

 -(void)mergeChanges:(NSNotification *)notification {
     NSManagedObjectContext *mainContext = [[NSApp delegate] managedObjectContext];
     [mainContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
                                   withObject:notification
                                waitUntilDone:YES];  
 }


 -(void)startOperation {
            // Omitted - Manage isExecuting, isPaused, isFinished etc flags

     imoc = [[NSManagedObjectContext alloc] init];
     [imoc setPersistentStoreCoordinator:[[NSApp delegate] persistentStoreCoordinator]];
     [imoc setUndoManager:nil];
     [imoc setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
     [imoc setStalenessInterval:0];

     [[NSNotificationCenter defaultCenter] addObserver:self
                                              selector:@selector(mergeChanges:) 
                                                  name:NSManagedObjectContextDidSaveNotification 
                                                object:imoc];
 }

 -(void)finishOperation {
            // Omitted - Manage isExecuting, isPaused, isFinished etc flags
 }

1 Ответ

1 голос
/ 14 ноября 2010

В ваших операциях используются разные «версии» сущности из магазина. Рассмотрим этот порядок операций:

Вы создаете 2 операции, назовем их O: F и O: G, которые должны добавить дочерние элементы F и G в группу 1, обозначенные как G: 1 с набором дочерних элементов [A, B, C, D, E ].

Очередь операций одновременно отключает O: F и O: G, поэтому они оба извлекают контекст управляемого объекта и сущность G: 1.

O: F устанавливает детей от G: 1 до [A, B, C, D, E, F]. O: G устанавливает детей из G: 2 в [A, B, C, D, E, G].

Неважно, какая операция выиграет, вы получите либо [A, B, C, D, E, F], либо [A, B, C, D, E, G], оба из которых являются неверные значения в магазине.

Я считаю, что CoreData должен выдавать оптимистическую ошибку блокировки в одном из этих потоков, поскольку его изменения устарели. Но я могу ошибаться.

Суть в том, что вы мутируете один и тот же объект в потоках без синхронизации состояния объекта. Вместо создания 20 операций создайте 1 операцию, которая добавляет 20 объектов, но у вас есть основная архитектурная проблема - попытка изменить один и тот же объект из нескольких потоков без синхронизации.

Это будет терпеть неудачу каждый раз.

...