Keep managedObjectContextDidUnregisterObjectsWithIDs: from blocking main thread

时间:2021-10-05 20:59:04

I have an app that performs background fetches to populate entities on a map. The fetch predicate is constantly updating as the user pans and zooms on the map or changes the search filter criteria. The queries take anywhere from a fraction of a second to several seconds to complete. In order to maintain a responsive user experience, I'm performing the fetches on the parent context on a private queue using the built-in main and parent contexts of UIManagedDocument.

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

I was experiencing blocking on the main thread when things attempted to access the main context, which required accessing the parent context in order to reach the persistent store to fetch faults, etc. I remedied all the affected parts of my own code by pre-fetching relationships and resolving faults before allowing further background fetches on the parent context.

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

But I am still experiencing blocking of the UI on occasion. When I pause the app during these moments of the UI being unresponsive, I get the following stack traces on the main and private queue threads:

但我偶尔会遇到阻止UI的问题。当我在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 ()

As you can see, the private queue is busy executing a fetch on the persistent store in sqlite. But the main thread has blocked because [_PFManagedObjectReferenceQueue _processReferenceQueue:] called [NSManagedObjectContext(_NestedContextSupport) managedObjectContextDidUnregisterObjectsWithIDs:], which blocked on a semaphore waiting to access the managed object context I'm assuming. I didn't call this code, but I'd like to know how to avoid it being called and blocking the main thread.

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

With each new set of NSManagedObjects returned by a query on the background thread, I fetch the objects on the main thread, relative to the main context. Then I place them on the map, first removing the old entities. Once I've done this, I allow the background thread to perform another fetch if one has been queued up from further action by the user.

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

It seems by the name of the method managedObjectContextDidUnregisterObjectsWithIDs:, this code has something to do with unregistering the entities that I just removed from the map, removing them from memory. Since this must be happening after a new background fetch has begun, is there a way I can expedite this occurring, to delay a new fetch until it does? Or is there a way to prevent it from blocking the main thread when it does happen?

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

1 个解决方案

#1


0  

If you are deploying for iOS8, you can take advantage of the new asynchronous fetch API. See the WWDC 2014 talk on Core Data for details.

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

For details on managedObjectContextDidUnregisterObjectsWithIDs see the documentation for NSIncrementalStore.

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

If you have to target older versions of iOS, I would suggest abandoning UIManagedDocument, and using MOCs in their raw form.

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

However, you have to understand that any time you want to access the exact same resource at the same time, there are limitations to what you can do.

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

In this case, you have a parent context, being busy doing IO, and the child context needs some work done... more importantly, it needs to coordinate with its parent context to do so. This causes your delay.

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

Without having more information on exactly what you are doing, I can suggest a few alternatives. First, use independent contexts. If you use case requires it, you can also use multiple Core data stacks, including multiple persistent store coordinators.

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

For high performance tasks, I have had success using a reader stack and a writer stack, with their own complete core data stack, including PSC.

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

#1


0  

If you are deploying for iOS8, you can take advantage of the new asynchronous fetch API. See the WWDC 2014 talk on Core Data for details.

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

For details on managedObjectContextDidUnregisterObjectsWithIDs see the documentation for NSIncrementalStore.

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

If you have to target older versions of iOS, I would suggest abandoning UIManagedDocument, and using MOCs in their raw form.

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

However, you have to understand that any time you want to access the exact same resource at the same time, there are limitations to what you can do.

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

In this case, you have a parent context, being busy doing IO, and the child context needs some work done... more importantly, it needs to coordinate with its parent context to do so. This causes your delay.

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

Without having more information on exactly what you are doing, I can suggest a few alternatives. First, use independent contexts. If you use case requires it, you can also use multiple Core data stacks, including multiple persistent store coordinators.

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

For high performance tasks, I have had success using a reader stack and a writer stack, with their own complete core data stack, including PSC.

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