核心数据和多线程

5

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

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

顺便说一句:我能够使用单个NSOperation使所有文件迭代并逐个解析。在此实现中,-mergeChanges:方法调用主线程MOC上的-mergeChangesFromContextDidSaveNotification:效果很好。

但理想情况下,我希望有一个NSOperation遍历源文件并生成解析每个文件的NSOperations。我尝试了几种方法,但似乎做不对。最有前途的方法是让每个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”。如果为真,则使用缓存的NSNotification在本地MOC上调用-mergeChangesFromContextDidSaveNotification。然后重置needsToMerge。如果通知是在本地发起的,则告诉主线程在其MOC上执行-mergeChangesFromContextDidSaveNotification。
我确信这不能正常工作,并且我会收到以下警告:
取消对当前线程堆栈上的objc代码调用会使其不安全。
我还尝试使用NSPeristentStoreCoordinator的锁来控制访问 - 但如果在调用NSManagedObjectContext的-save:方法时持有它,这可能会引起问题,因为-save:会通知感兴趣的观察者保存事件,并且-mergeChangesFromContextDidSaveNotification似乎会阻塞尝试获取PSC的锁。
看起来这应该更容易些。
2个回答

2

我认为我遇到了同样的问题,这是我解决它的方法:

创建一个自定义的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
@Westsider 所有需要合并上下文的NSOperation都应该是它的子类。基本上,我建议创建一个名为NSOperationManagedOobjectContextAware的类,它具有mergeChanges方法和3个属性changeNotifications、changeNotificationsLock、localManagedObjectContext。 - Elia Palme

0

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

- (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。


我不认为这种方法能解决我的问题。看起来所有合并都将在主线程中完成。我正在寻找的是一种让合并发生在多个非主线程中的方法,以便几个线程可以解析不同的文件但共享它们的增量进度。 - westsider
这是一种将解析的部分放入主 moc 的方法。我理解您的任务后,您必须稍后在主线程上编写代码。 - Alex
我正在尝试让多个线程读取写入共享的持久存储协调器,以便每个新添加的对象在添加后立即对所有线程可用。这与仅使主线程的托管对象上下文知道所有新添加的对象不同。 - westsider
合并更改后,您可以从主 moc 中获取它。 - Alex
performSelectorFromMainThread: 是单向的。如果按照我描述的方式使用同步上下文的方法,您可以立即同步部分数据。我不知道您需要哪种部分数据,但这必须是您的选择。 - Alex
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/Concurrency.html - James Bush

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接