ios - 保持 managedObjectContextDidUnregisterObjectsWithIDs : from blocking main thread

标签 ios objective-c multithreading core-data

我有一个应用程序执行后台提取以填充 map 上的实体。随着用户在 map 上平移和缩放或更改搜索过滤条件,获取谓词会不断更新。查询需要几分之一秒到几秒才能完成。为了保持响应式用户体验,我正在使用 UIManagedDocument 的内置主上下文和父上下文对私有(private)队列上的父上下文执行提取。 .

当事情试图访问主上下文时,我在主线程上遇到阻塞,这需要访问父上下文才能到达持久存储以获取错误等。我通过预取修复了我自己代码的所有受影响部分在允许对父上下文进行进一步的后台获取之前,关系和解决故障。

但我有时仍会遇到 UI 阻塞的情况。当我在 UI 无响应的这些时刻暂停应用程序时,我会在主队列线程和私有(private)队列线程上获得以下堆栈跟踪:

Thread 1Queue : com.apple.main-thread (serial)
#0  0x36a29568 in semaphore_wait_trap ()
#1  0x36ab4558 in _os_semaphore_wait ()
#2  0x0098ff0a in _dispatch_barrier_sync_f_slow ()
#3  0x28a4060c in _perform ()
#4  0x28a4d74e in -[NSManagedObjectContext(_NestedContextSupport) managedObjectContextDidUnregisterObjectsWithIDs:] ()
#5  0x289dc7ae in -[_PFManagedObjectReferenceQueue _processReferenceQueue:] ()
#6  0x289dc012 in _performRunLoopAction ()
#7  0x28c7f624 in __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ ()
#8  0x28c7cd08 in __CFRunLoopDoObservers ()
#9  0x28c7d10a in __CFRunLoopRun ()
#10 0x28bca980 in CFRunLoopRunSpecific ()
#11 0x28bca792 in CFRunLoopRunInMode ()
#12 0x2ff7c050 in GSEventRunModal ()
#13 0x2c1bc980 in UIApplicationMain ()
#14 0x0009a070 in main at main.m:17

Thread 288Queue : NSPersistentStoreCoordinator 0x176425c0 (serial)
#0  0x36ab71f8 in _platform_memmove$VARIANT$CortexA9 ()
#1  0x3670ab5e in ___lldb_unnamed_function194$$libsqlite3.dylib ()
#2  0x3670bd4e in ___lldb_unnamed_function195$$libsqlite3.dylib ()
#3  0x366f4cc8 in ___lldb_unnamed_function124$$libsqlite3.dylib ()
#4  0x366f23b4 in sqlite3_step ()
#5  0x289ba75c in _execute ()
#6  0x289ba454 in -[NSSQLiteConnection execute] ()
#7  0x289c77f4 in -[NSSQLCore _newRowsForFetchPlan:selectedBy:withArgument:] ()
#8  0x289c15e0 in -[NSSQLCore newRowsForFetchPlan:] ()
#9  0x289c0bd8 in -[NSSQLCore objectsForFetchRequest:inContext:] ()
#10 0x289c0564 in -[NSSQLCore executeRequest:withContext:error:] ()
#11 0x28a6ce98 in __65-[NSPersistentStoreCoordinator executeRequest:withContext:error:]_block_invoke ()
#12 0x28a7411e in gutsOfBlockToNSPersistentStoreCoordinatorPerform ()
#13 0x009889b6 in _dispatch_client_callout ()
#14 0x009901d8 in _dispatch_barrier_sync_f_invoke ()
#15 0x28a67c8e in _perform ()
#16 0x289c01de in -[NSPersistentStoreCoordinator executeRequest:withContext:error:] ()
#17 0x289bee92 in -[NSManagedObjectContext executeFetchRequest:error:] ()
#18 0x000d7f8c in __35-[MainViewController scheduleFetch]_block_invoke at MainViewController.m:1395
#19 0x28a45120 in developerSubmittedBlockToNSManagedObjectContextPerform ()
#20 0x00990e1c in _dispatch_queue_drain ()
#21 0x0098b2f4 in _dispatch_queue_invoke ()
#22 0x00992558 in _dispatch_root_queue_drain ()
#23 0x00993880 in _dispatch_worker_thread3 ()
#24 0x36ab9e24 in _pthread_wqthread ()
Enqueued from com.apple.main-thread (Thread 1)Queue : com.apple.main-thread (serial)
#0  0x0098f852 in _dispatch_barrier_async_f ()
#1  0x0098abd2 in dispatch_async_f ()
#2  0x000d7db4 in -[MainViewController scheduleFetch] at MainViewController.m:1391
#3  0x000d7086 in -[MainViewController setCurrentFetch:] at MainViewController.m:1331
#4  0x000d884a in __35-[MainViewController scheduleFetch]_block_invoke_2 at MainViewController.m:1443
#5  0x009889ca in _dispatch_call_block_and_release ()
#6  0x009889b6 in _dispatch_client_callout ()
#7  0x0098c410 in _dispatch_main_queue_callback_4CF ()
#8  0x28c7ec40 in __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ ()
#9  0x28c7d360 in __CFRunLoopRun ()
#10 0x28bca980 in CFRunLoopRunSpecific ()
#11 0x28bca792 in CFRunLoopRunInMode ()
#12 0x2ff7c050 in GSEventRunModal ()
#13 0x2c1bc980 in UIApplicationMain ()
#14 0x0009a070 in main at main.m:17
#15 0x36977aae in start ()

如您所见,私有(private)队列正忙于在 sqlite 中的持久存储上执行提取。但是主线程已经阻塞,因为[_PFManagedObjectReferenceQueue _processReferenceQueue:]调用[NSManagedObjectContext(_NestedContextSupport) managedObjectContextDidUnregisterObjectsWithIDs:] ,它阻塞了等待访问我假设的托管对象上下文的信号量。我没有调用此代码,但我想知道如何避免它被调用并阻塞主线程。

每组新的 NSManagedObject s 由后台线程上的查询返回,我在主线程上获取对象,相对于主上下文。然后我将它们放在 map 上,首先删除旧实体。完成此操作后,如果已排队等待用户的进一步操作,我将允许后台线程执行另一次提取。

似乎是方法的名称managedObjectContextDidUnregisterObjectsWithIDs: ,这段代码与取消注册我刚刚从 map 中删除的实体有关,将它们从内存中删除。由于这必须在新的后台提取开始后发生,有没有办法可以加快这种情况的发生,延迟新的提取直到它发生?或者有没有办法防止它在发生时阻塞主线程?

最佳答案

如果您正在为 iOS8 进行部署,则可以利用新的异步获取 API。有关详细信息,请参阅 WWDC 2014 关于核心数据的演讲。

详情请访问 managedObjectContextDidUnregisterObjectsWithIDs参见 NSIncrementalStore 的文档.

如果您必须针对旧版本的 iOS,我建议放弃 UIManagedDocument,并以原始形式使用 MOC。

但是,您必须了解,只要您想同时访问完全相同的资源,您可以做的事情就会受到限制。

在这种情况下,您有一个父上下文,正忙于做 IO,而子上下文需要完成一些工作……更重要的是,它需要与其父上下文协调才能这样做。这会导致您的延迟。

在没有关于您正在做什么的更多信息的情况下,我可以提出一些替代方案。首先,使用独立的上下文。如果您的用例需要,您还可以使用多个 Core 数据堆栈,包括多个持久存储协调器。

对于高性能任务,我已经成功地使用了读取器堆栈和写入器堆栈,以及它们自己完整的核心数据堆栈,包括 PSC。

关于ios - 保持 managedObjectContextDidUnregisterObjectsWithIDs : from blocking main thread,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26558958/

相关文章:

ios - 如何将变量传递给 UIButton 操作

objective-c - 不能与前面的 'type-name' 声明说明符组合

java - 如何在 android 的异步函数中连续接收 UDP?

iphone - IOS 是否有发送网页请求并在 iphone ios 上忘记它?

ios - 检测tableView何时在底部滞后

ios - 如何在 NSDictionary 中传递常量

ios - 如何水平定位 UINavigationBar 中的 backIndicatorImage

java - Executor服务设计模式

objective-c - getter 中的线程安全延迟初始化

iphone - 苹果通知 token 开启关闭