核心数据和线程/ Grand Central Dispatch

Posted

技术标签:

【中文标题】核心数据和线程/ Grand Central Dispatch【英文标题】:Core Data and threads / Grand Central Dispatch 【发布时间】:2011-11-24 08:14:53 【问题描述】:

我是 Grand Central Dispatch (GCD) 和 Core Data 的初学者,我需要您的帮助才能将 Core Data 与 CGD 一起使用,以便在我向 Core Data 添加 40.000 条记录时 UI 不会被锁定。

我知道CD不是线程安全的,所以我必须使用另一个上下文,然后保存数据并合并上下文,据我从一些文章中了解到。

我还不能做的就是把碎片拼在一起。

所以,在我的代码中,我需要你的帮助来解决这个问题。

我有:

/*some other code*/

for (NSDictionary *memberData in arrayWithResult) 

    //get the Activities for this member
    NSArray *arrayWithMemberActivities = [activitiesDict objectForKey:[memberData objectForKey:@"MemberID"]];

    //create the Member, with the NSSet of Activities
    [Members createMemberWithDataFromServer:memberData
                         andActivitiesArray:arrayWithMemberActivities
                              andStaffArray:nil
                           andContactsArray:nil
                     inManagedObjectContext:self.managedObjectContext];

如何将其转换为在后台运行,然后在保存完成后保存数据并更新 UI,而不会在保存 40.000 个对象时阻塞 UI?

【问题讨论】:

【参考方案1】:

这是一个很好的示例供您尝试。如果您有任何问题,请随时回来:

self.mainThreadContext... // This is a reference to your main thread context
NSPersistentStoreCoordinator *mainThreadContextStoreCoordinator = [self.mainThreadContext persistentStoreCoordinator];
dispatch_queue_t request_queue = dispatch_queue_create("com.yourapp.DescriptionOfMethod", NULL);
dispatch_async(request_queue, ^

    // Create a new managed object context
    // Set its persistent store coordinator
    NSManagedObjectContext *newMoc = [[NSManagedObjectContext alloc] init];
    [newMoc setPersistentStoreCoordinator:mainThreadContextStoreCoordinator]];

    // Register for context save changes notification
    NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
    [notify addObserver:self 
               selector:@selector(mergeChanges:) 
                   name:NSManagedObjectContextDidSaveNotification 
                 object:newMoc];

    // Do the work
    // Your method here
    // Call save on context (this will send a save notification and call the method below)
    BOOL success = [newMoc save:&error];
    if (!success)
        // Deal with error
    [newMoc release];
);
dispatch_release(request_queue);

并响应上下文保存通知:

- (void)mergeChanges:(NSNotification*)notification 

    dispatch_async(dispatch_get_main_queue(), ^
        [self.mainThreadContext mergeChangesFromContextDidSaveNotification:notification waitUntilDone:YES];
    );

完成后台线程上下文后,别忘了从通知中心移除观察者。

[[NSNotificationCenter defaultCenter] removeObserver:self];

【讨论】:

太棒了。谢谢。只是一点点');'在“dispatch_release(request_queue)”之前丢失。谢谢。 在发布新的MOC后我们不应该移除观察者吗? 是的,这听起来是个好主意。我有一个辅助方法,我在其中包装了我的后台处理任务,因此观察者通常会在该类的 dealloc 上被删除。 所以,你的意思是在 dealloc 中我应该像这样删除: [[NSNotificationCenter defaultCenter] removeObserver:self];您可以更新您的答案,以便其他人在查看此内容时清楚吗? @Rog 有更新/更好的方法吗?我使用了你的代码,但我的 UI 仍然处于锁定状态——我也查看了 MagicalRecord,无论我的 UI 被锁定了什么。【参考方案2】:

这是一个简单的描述 GCD 和 UI 的 sn-p。您可以将 doWork 替换为执行 CoreData 工作的代码。

关于 CD 和线程安全,GCD 的优点之一是您可以分割应用程序(子系统)的区域以同步并确保它们在同一个队列上执行。您可以在名为 com.yourcompany.appname.dataaccess 的队列上执行所有 CoreData 工作。

在示例中,有一个调用长时间运行的工作的按钮、一个状态标签,我添加了一个滑块以显示我可以在 bg 工作完成时移动滑块。

// on click of button
- (IBAction)doWork:(id)sender

    [[self feedbackLabel] setText:@"Working ..."];
    [[self doWorkButton] setEnabled:NO];

    // async queue for bg work
    // main queue for updating ui on main thread
    dispatch_queue_t queue = dispatch_queue_create("com.sample", 0);
    dispatch_queue_t main = dispatch_get_main_queue();

    //  do the long running work in bg async queue
    // within that, call to update UI on main thread.
    dispatch_async(queue, 
                   ^ 
                       [self performLongRunningWork]; 
                       dispatch_async(main, ^ [self workDone]; );
                   );

    // release queues created.
    dispatch_release(queue);    


- (void)performLongRunningWork

    // simulate 5 seconds of work
    // I added a slider to the form - I can slide it back and forth during the 5 sec.
    sleep(5);


- (void)workDone

    [[self feedbackLabel] setText:@"Done ..."];
    [[self doWorkButton] setEnabled:YES];

【讨论】:

你的例子很酷,但没有指定核心数据并发。还是谢谢。 关键是,如果您在应用程序中对子系统进行分区,并确保为该子系统排队的所有异步工作都使用相同的队列,队列会处理并发。 来自上面的帖子:“关于 CD 和线程安全,GCD 的优点之一是您可以分割应用程序(子系统)的区域以同步并确保它们在同一个队列上执行. 您可以在名为 com.yourcompany.appname.dataaccess 的队列上执行所有 CoreData 工作。” @bryanmac +1 有关如何获取对 UI 更新主线程的引用的示例。另外不要忘记释放队列,因为您是使用 dispatch_queue_create 自己创建的。 已更新代码中的版本,并发说明已发布。【参考方案3】:

这篇博文有关于Core Data并发和示例代码的详细描述: http://www.duckrowing.com/2010/03/11/using-core-data-on-multiple-threads/

【讨论】:

【参考方案4】:

添加另一个您可以检查的信息来源

ThreadedCoreData

Apple 的 ios 开发者库的示例代码,最近更新 (2013-06-09)

演示如何在多线程环境中使用 Core Data, 遵循核心数据中提到的第一个推荐模式 编程指南。

基于 SeismicXML 示例,它下载并解析 RSS 提要 美国地质调查局 (USGS) 提供的数据 最近世界各地的地震。是什么让这个样本与众不同 是它使用核心数据持续存储地震。每一次 您启动应用程序,它会下载新的地震数据,并以 NSOperation 检查重复和新创建的存储 地震作为管理对象。

对于那些刚接触 Core Data 的人来说,比较 SeismicXML 会很有帮助 用这个样品取样并注意必要的成分 在您的应用程序中引入 Core Data。

【讨论】:

【参考方案5】:

因此,选择的答案来自近 2 年前的现在,并且存在一些问题:

    它对 ARC 不友好 - 需要删除对 newMoc 的发布调用 - ARC 甚至无法编译 您应该在块内进行weakSelf / strongSelf 舞蹈 - 否则您可能会在创建观察者时创建一个保留循环。在此处查看 Apple 的文档:http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html @RyanG 在评论中询问他为什么要阻止。我的猜测是因为最近编辑的方法有 waitUntilDone:YES - 除了会阻塞主线程。您可能想要 waitUntilDone:NO,但我不知道这些更改事件是否也会触发 UI 更新,因此需要进行测试。

--编辑--

进一步研究#3 - waitUntilDone:YES 不是托管上下文对象的有效方法签名,那么它是如何工作的呢?

【讨论】:

【参考方案6】:

比将持久存储协调器附加到新上下文要简单得多,顺便说一句,这也不是线程安全的。

NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrency];
[context setParentContext:<main thread context here>];

[context performBlock:^

    ...
    // Execute all code on current context
    ...

];

NSError *error = nil;
[context save:&error];
if (!error) 
    [context.parentContext save:&error];
    if (error) 
        NSLog(@"Could not save parent context: %@", error);
    

else 
    NSLog(@"Could not save context: %@", error);

关于如何使用多上下文核心数据的精彩教程:

http://www.cocoanetics.com/2012/07/multi-context-coredata/

【讨论】:

以上是关于核心数据和线程/ Grand Central Dispatch的主要内容,如果未能解决你的问题,请参考以下文章

为 Core Data 创建一个仅在一个线程上的 Grand Central Dispatch 队列

暂停和恢复 Grand Central Dispatch 线程

Swift - 多线程实现方式 - Grand Central Dispatch(GCD)

iOS多线程编程(四)------ GCD(Grand Central Dispatch)

Grand Central Dispatch(GCD)

使用 Grand Central Dispatch (GCD) 创建恰好 N 个线程