3

我正在使用Core Data 唯一属性中描述的方法来防止两次具有相同的属性(该属性在此处称为 ID)。

这在单线程环境中运行良好。我使用多线程环境并使用每个线程范例一个上下文。

问题是如果两个线程同时尝试创建具有相同属性的对象,就会出现以下问题:

  • 线程 A 在 context1 中创建 ID 为 1 的对象,没有这样的对象,所以它被创建
  • 线程 B 在 context2 中创建 ID 为 1 的对象,没有这样的对象,所以它被创建
  • 线程 A 同步 context1->context2(使用下面的代码)

您发现自己有两条具有相同 ID (1) 的记录。

我在测试时看到了这个问题,所以它很少见,但随着时间的推移肯定会发生。

当然,有多种选择,如 GDC、信号量来防止这种情况发生,但在使用复杂的解决方案之前,我想知道是否有人有更好的解决方案,或者可以推荐在什么级别序列化事件。

使用 iOS5+ 和 ARC。

我使用此代码来同步我的上下文:

[[NSNotificationCenter defaultCenter] addObserver:self 
selector:@selector(backgroundContextDidSave:) 
name:NSManagedObjectContextDidSaveNotification                                                          
object:nil];

和:

- (void)backgroundContextDidSave:(NSNotification *)notification {
    /* Make sure we're on the main thread when updating the main context */
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(backgroundContextDidSave:)
                               withObject:notification
                            waitUntilDone:NO];
        return;
    }

    /* merge in the changes to the main context */
    for (NSManagedObjectContext* context in [self.threadsDictionary allValues]){
            [context mergeChangesFromContextDidSaveNotification:notification];
    }

}

要获得线程安全的上下文:

/**
 Returns the managed object context for the application.
 If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
 */
- (NSManagedObjectContext *) managedObjectContextForThread {

    // Per thread, give one back
    NSString* threadName = [NSString stringWithFormat:@"%d",[NSThread currentThread].hash];

    NSManagedObjectContext * existingContext = [self.threadsDictionary objectForKey:threadName];
    if (existingContext==nil){
        existingContext = [[NSManagedObjectContext alloc] init];
        [existingContext setPersistentStoreCoordinator: [self persistentStoreCoordinator]];
        [self.threadsDictionary setValue:existingContext forKey:threadName];
        [existingContext setMergePolicy:NSOverwriteMergePolicy];
    }

    return existingContext;
}
4

2 回答 2

0

我认为我找到了解决方案,仅使用 Core Data。

我现在使用 NSLock 来锁定上下文以模拟某种事务:

[[self managedObjectContextForThread] tryLock];

... read if ID1 exists ...

... write ID1 ...

[[self managedObjectContextForThread] unlock];

这似乎解决了这个问题(目前)。

于 2012-09-21T11:59:26.780 回答
0

我有一个相关的问题,但我正在使用更新的核心数据方法 - 使用上下文层次结构,它应该已经解决了这样的同步问题。

就我而言,当我尝试保存时,CoreData 内部发生了内部崩溃,CoreData 上下文正在验证属性的唯一性。我的验证是在 KVC 属性验证方法中实现的:

// Validate the catalogID for uniqueness. This implementation works fine in the following scenarios:
//  * end-editing,
//  * saving a new species entity,
//  * updating existing species entity.
-(BOOL)validateCatalogID:(id *)ioValue error:(NSError * __autoreleasing *)outError {

if (*ioValue == nil)
    return YES; // Let CoreData validate for empty/missing values

// Lazily prepare static request and predicate to fetch OTHER species catalogID's. We'll reuse these every time we validate the edited species catalogID.
static NSFetchRequest *otherSpeciesIDsFetchRequest = nil;
static NSPredicate *otherSpeciesCatalogIDsPredicate = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    otherSpeciesIDsFetchRequest = [NSFetchRequest fetchRequestWithEntityName:[[self entity] name]];
    otherSpeciesCatalogIDsPredicate = [NSPredicate predicateWithFormat:@"(catalogID == $ID) AND (self != $THIS_SPECIES)"];
});

@synchronized(otherSpeciesIDsFetchRequest) {
    // Configure the predicate with the current catalogID value to validate.
    otherSpeciesIDsFetchRequest.predicate = [otherSpeciesCatalogIDsPredicate predicateWithSubstitutionVariables:@{@"ID":*ioValue, @"THIS_SPECIES":self}];

    // instead of fectching entity data, we only COUNT matching items.
    NSError *error = nil;
    NSInteger count = [self.managedObjectContext countForFetchRequest:otherSpeciesIDsFetchRequest error:&error];
//    DBGLog(@"Check Species ID:%@ uniqueness, found %ld", *ioValue, (long)count);
    if (count > 0) {
        [self addValidationError:outError
                            code:ePMXexistingCatalogID
                         message:[NSString stringWithFormat:NSLocalizedString(@"ID '%@' is already used for another species. Use a unique ID", NULL), *ioValue]
                            ];
        return NO;
    }
    else {
        if (error!=nil) {
            [self addValidationError:outError code:ePMXexistingCatalogID
                             message:[NSString stringWithFormat:NSLocalizedString(@"Species ID '%@' cannot be validated. error %@", NULL), *ioValue, error]];
            return NO;
        }
    }
    return YES; // this catalogID was not found in any other species entity - unique - valid catalogID.
}
}

我看到的崩溃在堆栈中看起来像这样:

Crashed Thread:  0  Dispatch queue: com.apple.main-thread

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: EXC_I386_GPFLT

Application Specific Information:
objc_msgSend() selector name: subentitiesByName


 Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
 0   libobjc.A.dylib               objc_msgSend + 23
 1   com.apple.CoreData            -[NSManagedObjectContext countForFetchRequest:error:] + 235
 2   com.IOLR.PlanktoMetrix-II     -[PMSpecies(PMExtensions) validateCatalogID:error:] + 559
 3   com.apple.CoreData            -[NSManagedObject(_NSInternalMethods) _validateValue:forProperty:andKey:withIndex:error:] + 243
 4   com.apple.CoreData            -[NSManagedObject(_NSInternalMethods) _validatePropertiesWithError:] + 314
 5   com.apple.CoreData            -[NSManagedObject(_NSInternalMethods) _validateForSave:] + 106
 6   com.apple.CoreData            -[NSManagedObject validateForUpdate:] + 64
 7   com.IOLR.PlanktoMetrix-II     -[PMSpecies(PMExtensions) validateForUpdate:] + 73
 8   com.apple.CoreData            -[NSManagedObjectContext(_NSInternalAdditions) _validateObjects:forOperation:error:exhaustive:forSave:] + 619
 9   com.apple.CoreData            -[NSManagedObjectContext(_NSInternalAdditions) _validateChangesForSave:] + 410
 10  com.apple.CoreData            -[NSManagedObjectContext(_NSInternalChangeProcessing) _prepareForPushChanges:] + 208
 11  com.apple.CoreData            -[NSManagedObjectContext save:] + 247
 12  com.IOLR.PlanktoMetrix-II     -[BSManagedDocument contentsForURL:ofType:saveOperation:error:] + 918
 13  com.IOLR.PlanktoMetrix-II     __73-[BSManagedDocument saveToURL:ofType:forSaveOperation:completionHandler:]_block_invoke + 411
 14  com.apple.AppKit              -[NSDocument performAsynchronousFileAccessUsingBlock:] + 57
 15  com.IOLR.PlanktoMetrix-II     -[BSManagedDocument saveToURL:ofType:forSaveOperation:completionHandler:] + 370
 16  com.apple.AppKit              __67-[NSDocument autosaveWithImplicitCancellability:completionHandler:]_block_invoke + 1220
 17  com.apple.AppKit              -[NSDocument continueFileAccessUsingBlock:] + 234
 18  com.apple.AppKit              __54-[NSDocument performAsynchronousFileAccessUsingBlock:]_block_invoke681 + 125
 19  com.apple.AppKit              __62-[NSDocumentController(NSInternal) _onMainThreadInvokeWorker:]_block_invoke1807 + 175

解决了类似的问题后,您能提示一下我的实现吗?

于 2015-08-25T11:11:55.873 回答