2013-03-13 47 views
2

一个典型的设置:我们有一个mainMOC的主线程和一个自己的后台线程backgroundMOC。后台线程通过将块调度到backgroundQueue来对backgroundMOC执行只读操作。如何在跨线程合并更改时防止竞争条件?

backgroundMOC需要合并从mainMOC的变化,所以我们注册NSManagedObjectContextDidSaveNotification,然后像做

- (void)mainMocDidSave:(NSNotification *)notification { 
    dispatch_async(backgroundQueue, ^{ 
     [backgroundMoc mergeChangesFromContextDidSaveNotification:notification]; 
    }); 
} 

比方说,用户删除的mainMOC的对象。上面的代码对我来说似乎并不安全,因为合并将在未来的某个时间点完成。在合并完成之前,backgroundQueue上的块可能仍会尝试使用已删除的对象。

明显的解决方案是使用dispatch_sync代替(或performBlockAndWait,performSelector:OnThread:...)。从我在互联网上看到的代码片段来看,这似乎是每个人都在做的事情。但是我对这个解决方案也不舒服。

名称NSManagedObjectContextDidSaveNotification意味着保存已在通知发送时发生。所以相应的行已经从底层数据库中删除(假设一个sqlite存储)。 dispatch_sync必须等待队列上的其他块完成才能合并更改,而这些其他块仍然可以尝试使用已删除的对象,从而产生NSObjectInaccessibleException

在我看来,要合并从一个线程/队列到另一个变化的正确方法是

  1. 订阅NSManagedObjectContextWillSaveNotificationNSManagedObjectContextDidSaveNotification在后台线程。
  2. NSManagedObjectContextWillSaveNotification:清空backgroundQueue并暂停向队列分派新块的任何操作。
  3. NSManagedObjectContextDidSaveNotification:同步合并更改。
  4. 恢复对后台队列的正常操作。

这是正确的方法还是我错过了什么?

+0

作为后续:我不断收到死锁与上面的解决方案。看起来在某些情况下,主控线程在发送'NSManagedObjectContextWillSaveNotification'时已经获得了PSC上的锁定,这可能导致上面步骤2中所需的dispatch_sync调用在背景队列上仍有未完成的任务时一直等待。 – Lukas 2013-03-26 12:48:28

回答

0

我在两个项目中使用了以下结构,在这两个项目中我遇到过类似的麻烦。首先,我使用单件服务来确保只有一个后台线程合并和读取更改。

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]; 
     } 
    } 
} 

BackgroundService.m

- (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]; 
}