核心数据多线程:代码示例

Posted

技术标签:

【中文标题】核心数据多线程:代码示例【英文标题】:Core Data Multithreading: Code Examples 【发布时间】:2012-09-17 20:43:54 【问题描述】:

我的多线程 Core Data 应用程序一直在使用 problems,我想我应该认真看看我在做什么以及如何做。请让我知道以下是否可行。

我有一个单独的 DataManager 类来处理核心数据的东西。它有一个属性managedObjectContext,为每个线程返回不同的MOC。所以,给定NSMutableDictionary *_threadContextDict(字符串线程名称到上下文)和NSMutableDictionary *_threadDict(字符串线程名称到线程),它看起来像这样:

-(NSManagedObjectContext *)managedObjectContext

  if ([NSThread currentThread] == [NSThread mainThread])
  
    MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
    return delegate.managedObjectContext; //MOC created in delegate code on main thread
  
  else
  
    NSString *thisThread = [[NSThread currentThread] description];
    
      if ([_threadContextDict objectForKey:thisThread] != nil)
      
        return [_threadContextDict objectForKey:thisThread];
      
      else
      
        NSManagedObjectContext *context = [[NSManagedObjectContext alloc]init];
        MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
        [context setPersistentStoreCoordinator:delegate.persistentStoreCoordinator];
        [_threadContextDict setObject:context forKey:thisThread];
        [_threadDict setObject:[NSThread currentThread] forKey:thisThread];

        //merge changes notifications
        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
        [center addObserver:self selector:@selector(mergeChanges:)
            name:NSManagedObjectContextDidSaveNotification object:context];

        return context;
      
    
  

mergeChanges 方法中,我将来自传入通知的更改合并到除生成通知的上下文之外的所有上下文。它看起来像这样:

-(void)mergeChanges:(NSNotification *)notification

  MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
  NSManagedObjectContext *context = delegate.managedObjectContext;
  [context performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification)
     withObject:notification waitUntilDone:YES];

  for (NSString *element in [_threadContextDict allKeys])
  
    if (![element isEqualToString:[[NSThread currentThread] description]])
    
      NSThread *thread = [_threadDict objectForKey:element];
      NSManagedObjectContext *threadContext = [_threadContextDict objectForKey:element];
      [threadContext performSelector:@selector(mergeChangesFromContextDidSaveNotification)
         onThread:thread withObject:notification waitUntilDone:YES];
    
  

每当我在 MOC 上保存更改时,都是通过调用此共享 DataManager 上的 saveContext 方法来完成的,该方法在从上述属性获得的上下文中调用 save

-(void)saveContext

  NSManagedObjectContext *context = self.managedObjectContext;
  NSError *err = nil;
  [context save:&err];
  //report error if necessary, etc.

鉴于我对 Core Data 多线程规则的理解,我觉得这应该可行。我为每个线程使用单独的上下文,但对所有线程使用相同的持久存储。但是当我使用它时,我会遇到很多合并冲突,即使我的线程没有处理相同的对象(NSManagedObject 子类)。我只是从网络上下载数据,解析结果,然后将它们保存到 Core Data。

我做错了吗?我试过使用NSLock 实例来锁定一些东西,但后来我就挂了。

更新/解决方案:我可以通过添加一个简单的东西来完成这项工作:一种在我完成后从字典中删除线程/MOC 对的方法。在每次调用dispatch_async 的每个块结束时,我都会调用[self removeThread],它会从字典中删除当前线程及其MOC。我也只合并对主线程 MOC 的更改。实际上,这意味着每次我在后台线程上工作时,我都会得到一个全新的 MOC。

我还通过向userInfoDict 添加一个数字来区分线程,而不是调用description。该数字是通过我的类上的只读属性获得的,每次调用它时都会返回一个更高的数字。

【问题讨论】:

您接受了一个答案,但您能解决您的问题吗?你做了什么? developer.apple.com/library/content/documentation/Cocoa/… 【参考方案1】:

恕我直言,您的方法是一场噩梦,如果出现问题,调试它以解决任何问题应该更糟糕。第一个问题是这样的:

我有一个单例数据管理器

没有一个单例对象来管理不同线程上不同实体的核心数据操作。单例处理起来很棘手,尤其是在多线程环境中,并且将其与核心数据一起使用是一种更糟糕的方法。

第二件事,不要使用 NSThread 来处理多线程。还有更现代的 API。使用 Grand Central 调度或 NSOperation/NSOperationQueue。自从引入块(ios 4)以来,Apple 一直鼓励人们从 NSThread 迁移。并且为了将来参考,不要以您使用对象的方式使用对象的描述。描述通常/主要用于调试目的。那里的信息不应该用来比较。甚至没有指针值(这就是为什么你应该使用 isEqual 而不是 ==)。

这是您需要了解的有关核心数据和多线程的知识:

    为每个线程创建一个上下文。核心数据模板已经为您创建了一个主线程上下文。在后台线程执行开始时(在块内,或在您的 NSOperation 子类的 main 方法上),初始化您的上下文。 一旦你的上下文被初始化,并且有正确的 persistentStoreCoordinator,监听 NSManagedObjectContextObjectsDidChangeNotification。监听通知的对象将在保存上下文的同一线程中接收通知。由于这与主线程不同,因此在使用接收上下文的线程上使用合并上下文进行合并调用。假设您在与主线程不同的线程内使用上下文,并且您想与主线程合并,您需要在主线程内调用 merge 方法。你可以这样做 dispatch_async(dispatch_get_main_queue(), ^//code here); 不要在其 managedObjectContext 所在的线程之外使用 NSManagedObject。

有了这些和其他简单的规则,在多线程环境下管理核心数据就更容易了。你的方法更难实现,更难调试。对您的架构进行一些更改。根据您正在使用的线程(而不是集中式)管理上下文。不要将对上下文的引用保留在其范围之外。创建第一个上下文后,在线程上创建上下文并不昂贵。您可以重复使用相同的上下文,只要它在同一个块/NSOperation 执行中。

【讨论】:

感谢您的诚实。不过,我不明白为什么我的方法不遵守这些规则。我的意图是抽象出这些规则,这样我就不必在我的代码中担心它们。我正在使用NSThread,因为我假设我需要合并所有上下文中的更改(我错了吗?),并且必须在每个上下文的线程上完成。除了主线程的dispatch_get_main_queue() 之外,我还没有找到使用 GCD 指定块应该运行的线程的方法。 嗯,首先,您不应该保留引用以访问不属于您的线程。连排队都不行。如果您想要一个队列,请使用 dispatch_queue_create("com.aname.aname", DISPATCH_QUEUE_SERIAL 或 DISPATCH_QUEUE_CONCURRENT) 创建自己的队列。要获取后台队列,可以使用 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^//code here);。如果你用短暂的生命来保持你的上下文,保持同步会更容易。查看相关文档信息: 您在一个上下文中对托管对象所做的更改不会传播到不同上下文中的相应托管对象,除非您重新获取或重新故障该对象。如果您需要在一个线程中跟踪对另一个线程中的托管对象所做的更改,您可以采用两种方法,都涉及通知。出于解释的目的,考虑两个线程“A”和“B”,并假设您要将更改从 B 传播到 A。

以上是关于核心数据多线程:代码示例的主要内容,如果未能解决你的问题,请参考以下文章

多线程的常用关键字及示例代码(synchronizedvolatile)

python的多线程多进程代码示例

Java多线程:线程同步详解

多线程之脏读 代码示例及处理

python下多线程是鸡肋,推荐使用多进程 代码示例

11,多线程示例代码