保持managedObjectContextDidUnregisterObjectsWithIDs:阻止主线程

时间:2014-10-25 03:06:35

标签: ios objective-c multithreading core-data

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

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

但我偶尔会遇到阻止用户界面的问题。当我在UI没有响应的这些时刻暂停应用程序时,我在主队列线程和专用队列线程上获得以下堆栈跟踪:

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

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

对于后台线程上的查询返回的每个新NSManagedObject集合,我获取主线程上相对于主上下文的对象。然后我将它们放在地图上,首先删除旧实体。一旦我完成了这项工作,我允许后台线程执行另一次获取,如果有人已经从用户的进一步操作中排队等待。

似乎通过方法managedObjectContextDidUnregisterObjectsWithIDs:的名称,此代码与取消注册我刚从地图中删除的实体,从内存中删除它们有关。因为这必须在新的后台提取开始之后发生,有没有办法可以加快这种情况发生,推迟新的提取直到它呢?或者有没有办法防止它在发生时阻塞主线程?

1 个答案:

答案 0 :(得分:0)

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

有关managedObjectContextDidUnregisterObjectsWithIDs的详细信息,请参阅NSIncrementalStore的文档。

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

但是,您必须明白,只要您想同时访问完全相同的资源,您就可以做些限制。

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

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

对于高性能任务,我成功使用了读取器堆栈和写入器堆栈,并拥有自己的完整核心数据堆栈,包括PSC。