我一直在为同样的问题苦苦挣扎一段时间。到目前为止关于这个问题的讨论给了我一些想法,我现在将分享这些想法。
请注意,这基本上是未经测试的,因为在我的情况下,我在测试期间很少看到这个重复的问题,而且我没有明显的方法可以轻松地重现它。
我有相同的 CoreData 堆栈设置 - 私有队列上的主 MOC,主队列上有一个子队列,它用作应用程序的主上下文。最后,批量导入操作(查找或创建)使用后台队列传递到第三个 MOC。操作完成后,保存将传播到 PSC。
我已经将我的所有核心数据堆栈从 AppDelegate 移到了一个单独的类 ( AppModel),该类为应用程序提供了对域的聚合根对象() 的访问权限,Player并且还提供了一个用于在模型上执行后台操作的辅助函数 ( performBlock:onSuccess:onError:)。
对我来说幸运的是,所有主要的 CoreData 操作都是通过这种方法汇集起来的,所以如果我能确保这些操作是串行运行的,那么重复的问题就应该得到解决。
- (void) performBlock: (void(^)(Player *player, NSManagedObjectContext *managedObjectContext)) operation onSuccess: (void(^)()) successCallback onError:(void(^)(id error)) errorCallback
{
    //Add this operation to the NSOperationQueue to ensure that 
    //duplicate records are not created in a multi-threaded environment
    [self.operationQueue addOperationWithBlock:^{
        NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [managedObjectContext setUndoManager:nil];
        [managedObjectContext setParentContext:self.mainManagedObjectContext];
        [managedObjectContext performBlockAndWait:^{
            //Retrive a copy of the Player object attached to the new context
            id player = [managedObjectContext objectWithID:[self.player objectID]];
            //Execute the block operation
            operation(player, managedObjectContext);
            NSError *error = nil;
            if (![managedObjectContext save:&error])
            {
                //Call the error handler
                dispatch_async(dispatch_get_main_queue(), ^{
                    NSLog(@"%@", error);
                    if(errorCallback) return errorCallback(error);
                });
                return;
            }
            //Save the parent MOC (mainManagedObjectContext) - WILL BLOCK MAIN THREAD BREIFLY
            [managedObjectContext.parentContext performBlockAndWait:^{
                NSError *error = nil;
                if (![managedObjectContext.parentContext save:&error])
                {
                    //Call the error handler
                    dispatch_async(dispatch_get_main_queue(), ^{
                        NSLog(@"%@", error);
                        if(errorCallback) return errorCallback(error);
                    });
                    return;
                }
            }];
            //Attempt to clear any retain cycles created during operation
            [managedObjectContext reset];
            //Call the success handler
            dispatch_async(dispatch_get_main_queue(), ^{
                if (successCallback) return successCallback();
            });
        }];
    }];
}
我在这里添加的希望能为我解决问题的内容是将整个内容包装在addOperationWithBlock. 我的操作队列简单配置如下:
single.operationQueue = [[NSOperationQueue alloc] init];
[single.operationQueue setMaxConcurrentOperationCount:1];
在我的 API 类中,我可能会按如下方式对我的操作执行导入:
- (void) importUpdates: (id) methodResult onSuccess: (void (^)()) successCallback onError: (void (^)(id error)) errorCallback
{
    [_model performBlock:^(Player *player, NSManagedObjectContext *managedObjectContext) {
        //Perform bulk import for data in methodResult using the provided managedObjectContext
    } onSuccess:^{
        //Call the success handler
        dispatch_async(dispatch_get_main_queue(), ^{
            if (successCallback) return successCallback();
        });
    } onError:errorCallback];
}
现在,随着NSOperationQueue时间的推移,应该不再可能同时进行多个批处理操作。