coreData executeFetchRequest 方法的 Race Condition 导致 nil 数据的问题

Posted

技术标签:

【中文标题】coreData executeFetchRequest 方法的 Race Condition 导致 nil 数据的问题【英文标题】:Race Condition of coreData executeFetchRequest method cause the issue of nil data 【发布时间】:2013-08-21 15:41:05 【问题描述】:

我遇到了一个我不知道如何解决它的竞争条件。 getPurchasedBookTitles 和 getBookEntryIDs 使用 NSOperationQueue 在不同的线程中,它们相互竞争调用 fetchBooks(这是不同类中的方法)。我将 managedObjecContext (parent, child, root) 与 lock/unlock 一起放置以避免赛车问题,但它似乎并没有解决根本问题。

问题是 for 循环中的 book.bookTitle (Book *book in allBooks) 有时会变为 nil 并导致应用程序崩溃或挂起。

提前致谢!

// 在单例类中,dispatch_once

- (NSArray *)getPurchasedBookTitles

    NSMutableArray *bookTitles = [[NSMutableArray alloc] init];

    NSArray *allBooks = [[CoreDataManager sharedInstance] fetchBooks];

    for (Book *book in allBooks)
    
        if (book.bookTitle != nil && book.is_owned != nil )
            if (![bookTitles containsObject:book.bookTitle] && [[book is_owned] boolValue] == YES)
                [bookTitles addObject:book.bookTitle];
    

    return bookTitles;


- (NSArray *)getBookEntryIDs
    
    NSMutableArray *bookTitles = [[NSMutableArray alloc] init];

    NSArray *allBooks = [[CoreDataManager sharedInstance] fetchBooks];

    for (Book *book in allBooks)
    
        if (book.bookTitle != nil)
            if (![bookTitles containsObject:book.bookTitle])
                [bookTitles addObject:book.bookTitle];
    

    return bookTitles;

// 在 coreDataManager 类中 // managedObjectContextChild、managedObjectContext、writerManagedObjectContext在appDelegate和coreDataManager类中详细声明和实例化。

- (NSArray *)fetchBooks
        
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"bookTitle" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    [request setEntity:bookEntity];
    [request setPredicate:nil];
    [request setSortDescriptors:sortDescriptors];

    [managedObjectContextChild lock];
    [managedObjectContext lock];
    [writerManagedObjectContext lock];

    NSError *error = NULL;

    NSArray *results = [managedObjectContextChild executeFetchRequest:request error:&error];

    if (error != NULL)
        NSLog(@"Error fetching - %@", error);

    [writerManagedObjectContext unlock];
    [managedObjectContext unlock];
    [managedObjectContextChild unlock];

    return results;

【问题讨论】:

【参考方案1】:

Core Data 不是线程安全的,您需要采取措施来处理它。我不确定是什么将 bookTitle 设置为 nil,但上面的代码存在几个主要问题:

    “非线程安全”意味着您不能在多个线程上使用 Core Data 对象根本,除非您可以保证 所有 访问是同步的。通过锁同步获取只涵盖了它的一部分——你也不能同时在多个线程上使用获取的对象。您将在多个线程上使用相同的 Book 实例,这几乎可以保证问题。通常的方法是为不同的线程/队列提供自己的托管对象上下文,并在进行更改时同步更改。

    锁定可以提供帮助,但前提是您确定您已经找到了所有可能性。很难做到正确并且容易陷入僵局。这就是为什么 Core Data 包含一个专门设计用于处理这种情况的 API。在此处查看 performBlockperformBlockAndWait 方法以获取帮助。使用这些而不是您自己的锁定方案。

【讨论】:

感谢您的帮助,但是对象 Book 是在不同的线程上分别获取以供不同的方法使用... getPurchasedBookTitles 和 getBookEntryIDs 在不同的线程上运行。 是的,这正是问题所在。您从不同线程上的相同托管对象上下文中获取相同的 Core Data 实例,然后在不同线程上使用相同的实例。事实上,您在没有适当预防措施的情况下使用不同的线程是整个问题。 首先在这里使用 managedObjectContext 锁是个好主意吗? 不如使用多个托管对象上下文或使用块 API 好。

以上是关于coreData executeFetchRequest 方法的 Race Condition 导致 nil 数据的问题的主要内容,如果未能解决你的问题,请参考以下文章

CoreData 本地数据存储

CoreData与CloudKit同步时将图像保存到CoreData?

RestKit + CoreData:从CoreData缓存中排除某些对象

Coredata — 入门使用

认识CoreData—初识CoreData

Coredata 单表简单使用