【问题标题】:Core Data and multithreading核心数据和多线程
【发布时间】:2011-01-15 20:44:38
【问题描述】:

我已经阅读了 Marcus Zarra 在他的 Core Data 书中关于多线程的一章,并且相当仔细地查看了他的示例代码。但是他的代码和我在其他地方找到的其他代码似乎都集中在后台进程中,而 不需要 需要相互了解。这些示例适用于导入树形结构 - 但它们不能解决更一般(复杂)结构的导入问题,例如有向无环图。

就我而言,我正在尝试解析 C++ 类层次结构,并希望使用尽可能多的 NSOperations。我想为每个遇到的类创建一个 NSManagedObject 实例,并且我想在保存时合并不同的 NSManagedObjectContexts。

顺便说一句:我能够使用一个迭代文件并一次解析一个的 NSOperation 来处理事情。在这个实现中,在主线程的 MOC 上调用 -mergeChangesFromContextDidSaveNotification: 的 -mergeChanges: 方法效果很好。

但理想情况下,我会让一个 NSOperation 迭代源文件并生成 NSOperation 来解析每个文件。我尝试了几种方法 - 但似乎无法做到正确。最有希望的是让每个 NSOperation 观察 NSManagedObjectContextDidSaveNotification。使用 -mergeChanges: 看起来像这样:

- (void) mergeChanges:(NSNotification *)notification
 {
 // If locally originated, then trigger main thread to merge.
 if ([notification object] == [self managedObjectContext])
  {
  AppDelegate *appDelegate = (AppDelegate*)[[NSApplication sharedApplication] delegate];
  NSManagedObjectContext *mainContext = [appDelegate managedObjectContext];

  // Merge changes into the main context on the main thread
  [mainContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
         withObject:notification
         waitUntilDone:YES];
  return;
  }
   // If not locally originated, then flag need to merge with in this NSOperation's thread.  
 [self setNeedsToMerge:YES];
 [self setMergeNotification:notification];
 }

本质上,解析 NSOperation 的 main() 会定期检查 ivar 'needsToMerge'。如果是真的,那么 -mergeChangesFromContextDidSaveNotification: 在本地 MOC 上被调用并缓存了 NSNotification。然后 needsToMerge 被重置。如果通知来自本地,则通知主线程在其 MOC 上执行 -mergeChangesFromContextDidSaveNotification:。

我确信这有一个很好的理由为什么这不起作用以及为什么我得到这个:

警告:取消调用 - objc 代码 在当前线程的堆栈上 这不安全。

我也尝试使用 NSPeristentStoreCoordinator 的锁来控制访问 - 但如果它在调用 NSManagedObjectContext 的 -save: 方法期间被持有,这是有问题的,因为 -save: 将通知感兴趣的观察者保存事件和 -mergeChangesFromContextDidSaveNotification: 似乎阻塞试图获取 PSC 的锁。

似乎这应该容易得多。

【问题讨论】:

    标签: multithreading core-data nsmanagedobjectcontext


    【解决方案1】:

    我想我也遇到过同样的问题,我是这样解决的:

    在您定义的地方创建一个自定义 NSOperation 类:

    NSMutableArray * changeNotifications;
    NSLock  * changeNotificationsLock;
    NSManagedObjectContext  * localManagedObjectContext;
    

    在您的 NSOperation 主方法中,在保存上下文之前首先应用所有请求的更改:

    [self.changeNotificationsLock lock];
    for(NSNotification * change in self.changeNotifications){
        [self.localManagedObjectContext mergeChangesFromContextDidSaveNotification:change];
    }
    if([self.changeNotifications count] >0){
        [self.changeNotifications removeAllObjects];
    }
    [self.changeNotificationsLock unlock];
    
    NSError *error = nil;   
    [self.localManagedObjectContext save:&error]
    

    请注意,我使用了锁,这是因为 NSMutableArray 不是线程安全的,我想安全地访问 changeNotifications。 changeNotifications 是存储在保存上下文之前需要应用的所有更改的数组。

    这是您的合并方法,已修改,以便您的 NSOperation 需要合并的所有更改都使用正确的线程合并。请注意,此方法由您的 NSOperation 之外的其他线程调用,因此您需要锁定对 self.changeNotifications 的访问

    - (void) mergeChanges:(NSNotification *)notification
     {
     // If not locally originated, then add notification into change notification array
     // this notification will be treated by the NSOperation thread when needed.
     if ([notification object] != self.localManagedObjectContext)
      {
         [self.changeNotificationsLock lock];
         [self.changeNotifications addObject:notification];
         [self.changeNotificationsLock unlock];
      }
    
     //Here you may want to trigger the main thread to update the main context     
    
    }
    

    希望对您有所帮助!此方法并非 100% 坚如磐石,在某些情况下更改通知可能会为时已晚。在这种情况下,上下文保存方法将返回错误,您必须重新加载 NSManagedObject 并再次保存。 如果需要更多详细信息,请告诉我。

    【讨论】:

    • 这是一个不错的解决方案。对我来说,除了保存之前,我可能需要在搜索对象之前应用 changeNotifications - 但这很容易。据我了解,所有线程(主线程除外)都是该子类的实例。是这样吗?
    • @Westsider 所有需要合并上下文的 NSOperation 都应该是它的子类。基本上我建议创建一个类 NSOperationManagedOobjectContextAware,它有一个 mergeChanges 方法和 3 个属性 changeNotifications、changeNotificationsLock、localManagedObjectContext
    【解决方案2】:

    这两种代码现在可以在我的应用程序中正常工作:

    - (void)mergeChanges:(NSNotification *)notification;
    {
    //AppDelegate *appDelegate = [[NSApplication sharedApplication] delegate];
    NSManagedObjectContext *mainContext = [appDelegate managedObjectContext];
    
    // Merge changes into the main context on the main thread
    [mainContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) 
                                  withObject:notification
                               waitUntilDone:YES];  
    }
    
    -(void) main {
    // Register context with the notification center
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 
    [nc addObserver:self
           selector:@selector(mergeChanges:) 
               name:NSManagedObjectContextDidSaveNotification
             object:managedObjectContext];
    

    当然,managedObjectContext 的意思是:

    managedObjectContext = [[NSManagedObjectContext alloc] init];
    [managedObjectContext setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]];
    [managedObjectContext setUndoManager:nil];
    

    如果您需要在从主 moc 进行更新之前删除某些内容,请小心。我有很多时间和很多难以调试的错误,而我知道我不能在主线程中使用 moc 而在任何地方处理具有相同内容的其他更改。

    【讨论】:

    • 我不明白这将如何解决我的问题。看起来 all 合并将在主线程中完成。我正在寻找一种在多个非主线程中进行合并的方法,以便多个线程可以解析不同的文件但共享它们的增量进度。
    • 这是一种将部分解析放到主 moc 的方法。当我理解你的任务时,你必须稍后在主线程上做你的代码。
    • 我正在尝试让多个线程读取写入共享持久存储协调器,这样每个新添加的对象都可用于所有线程一旦添加。这与简单地让主线程的托管对象上下文了解所有新添加的对象不同。
    • 合并更改后,您可以从主 moc 中获取它。
    • performSelectorFromMainThread: 是一种方式。如果按照我描述的方式使用同步我的上下文的方式,您可以使用立即同步部分数据。我不知道你需要什么样的部分数据,但这必须是你的选择。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-02
    • 2011-02-07
    • 2013-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多