Multithreading 如何在跨线程合并更改时防止争用情况?

Multithreading 如何在跨线程合并更改时防止争用情况?,multithreading,core-data,merge,race-condition,Multithreading,Core Data,Merge,Race Condition,典型的设置:我们有一个带有mainMOC的主线程和一个带有自己backgroundMOC的后台线程。后台线程通过将块分配到backgroundQueue对backgroundMOC执行只读操作 backgroundMOC需要合并来自mainMOC的更改,因此我们注册了nsmanagedobjectcontextdidsaveinnotification,然后执行类似操作 - (void)mainMocDidSave:(NSNotification *)notification { dis

典型的设置:我们有一个带有
mainMOC
的主线程和一个带有自己
backgroundMOC
的后台线程。后台线程通过将块分配到
backgroundQueue
backgroundMOC
执行只读操作

backgroundMOC
需要合并来自
mainMOC
的更改,因此我们注册了
nsmanagedobjectcontextdidsaveinnotification
,然后执行类似操作

- (void)mainMocDidSave:(NSNotification *)notification {
    dispatch_async(backgroundQueue, ^{
        [backgroundMoc mergeChangesFromContextDidSaveNotification:notification];
    });
}
假设用户删除了
mainMOC
中的一个对象。上面的代码对我来说并不安全,因为合并将在将来的某个时候完成。在合并完成之前,
backgroundQueue
上可能仍有块试图使用已删除的对象

显而易见的解决方案是使用
调度同步
(或者
执行锁定和等待
执行选择器:OnThread:…
)。从我在互联网上看到的代码片段来看,这似乎是每个人都在做的事情。但我对这个解决方案也不满意

名称
NSManagedObjectContextDidSaveNotification
表示在发送通知时已进行保存。因此,相应的行已经从基础数据库中删除(假设是sqlite存储)
dispatch\u sync
必须等待队列上的其他块完成,然后才能合并更改,而这些其他块仍可以尝试使用已删除的对象,从而导致出现
NSObjectInaccessibleException

在我看来,将更改从一个线程/队列合并到另一个线程/队列的正确方法是

  • 在后台线程上订阅
    NSManagedObjectContextWillSaveNotification
    NSManagedObjectContextDidSaveNotification
  • NSManagedObjectContextWillSaveNotification
    上:清空
    backgroundQueue
    并挂起向队列分配新块的任何操作
  • 在NSManagedObjectContextDidSaveNotification上:同步合并更改
  • 恢复后台队列上的正常操作

  • 这是正确的方法还是我遗漏了什么?

    我在两个项目中使用了以下结构,其中我遇到了与您类似的问题。首先,我使用singleton服务来确保只有一个后台线程合并和读取更改

    AppDelegate.m

    - (NSManagedObjectContext *)managedObjectContext {
        if (_managedObjectContext != nil) {
            return _managedObjectContext;
        }
    
        NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
        if (coordinator != nil) {
            // It is crucial to use the correct concurrency type!
            _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
            [_managedObjectContext setPersistentStoreCoordinator:coordinator];
        }
        return _managedObjectContext;
    }
    
    - (void)saveContext {
        NSError *error = nil;
        NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
        if (managedObjectContext != nil) {
            if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
                 // Replace this implementation with code to handle the error appropriately.
                 // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 
                NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
                abort();
            }
            else {
                [[NSNotificationCenter defaultCenter] postNotificationName:@"ParentContextDidSaveNotification" object:nil];
            }
        }
    }
    
    - (id)init {
        self = [super init];
    
        if (self) {
            [self managedObjectContext];
            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(parentContextDidSave) name:@"ParentContextDidSaveNotification" object:nil];
        }
    
        return self;
    }
    
    - (NSManagedObjectContext *)managedObjectContext {
        if (!_managedObjectContext) {
            // Again, make sure you use the correct concurrency type!
            _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
            [_managedObjectContext setParentContext:[(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext]];
        }
    
        return _managedObjectContext;
    }
    
    
    - (BOOL)saveContext {
        @synchronized(self) {
            BOOL successful = YES;
    
            // Bad practice, process errors appropriately.
            [[self managedObjectContext] save:nil];
    
            [[[self managedObjectContext] parentContext] performBlock:^{
                [(AppDelegate *)[[UIApplication sharedApplication] delegate] saveContext];
            }];
    
            return successful;
        }
    }
    
    - (void)parentContextDidSave {
        [[self managedObjectContext] reset];
    
        [[NSNotificationCenter defaultCenter] postNotificationName:@"ManagedObjectContextResetNotification" object:nil];
    }
    
    BackgroundService.m

    - (NSManagedObjectContext *)managedObjectContext {
        if (_managedObjectContext != nil) {
            return _managedObjectContext;
        }
    
        NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
        if (coordinator != nil) {
            // It is crucial to use the correct concurrency type!
            _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
            [_managedObjectContext setPersistentStoreCoordinator:coordinator];
        }
        return _managedObjectContext;
    }
    
    - (void)saveContext {
        NSError *error = nil;
        NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
        if (managedObjectContext != nil) {
            if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
                 // Replace this implementation with code to handle the error appropriately.
                 // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 
                NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
                abort();
            }
            else {
                [[NSNotificationCenter defaultCenter] postNotificationName:@"ParentContextDidSaveNotification" object:nil];
            }
        }
    }
    
    - (id)init {
        self = [super init];
    
        if (self) {
            [self managedObjectContext];
            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(parentContextDidSave) name:@"ParentContextDidSaveNotification" object:nil];
        }
    
        return self;
    }
    
    - (NSManagedObjectContext *)managedObjectContext {
        if (!_managedObjectContext) {
            // Again, make sure you use the correct concurrency type!
            _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
            [_managedObjectContext setParentContext:[(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext]];
        }
    
        return _managedObjectContext;
    }
    
    
    - (BOOL)saveContext {
        @synchronized(self) {
            BOOL successful = YES;
    
            // Bad practice, process errors appropriately.
            [[self managedObjectContext] save:nil];
    
            [[[self managedObjectContext] parentContext] performBlock:^{
                [(AppDelegate *)[[UIApplication sharedApplication] delegate] saveContext];
            }];
    
            return successful;
        }
    }
    
    - (void)parentContextDidSave {
        [[self managedObjectContext] reset];
    
        [[NSNotificationCenter defaultCenter] postNotificationName:@"ManagedObjectContextResetNotification" object:nil];
    }
    

    作为跟进:我一直在用上面的解决方案陷入僵局。在某些情况下,当主线程发送
    NSManagedObjectContextWillSaveNotification
    时,PSC上的锁似乎已经被主线程获取,这可能会导致上面步骤2中所需的调度同步调用在后台队列上仍有未完成的任务时永远等待。