Hang in mergeChangesFromContextDidSaveNotification

2019-09-08 18:44发布

问题:

In summary: My app is hanging when I call [myMOC mergeChangesFromContextDidSaveNotification:notification] in a multithreaded scenario.

The detailed situation is this:

My app downloads a whole bunch of data from a server and stores it in Core Data on first launch. It comes in several parts. Parsing the whole thing takes several seconds, and most of that time is spent on two of the chunks. So in order to speed it up, I've parallelized those two chunks. This is what it looks like:

NSArray *arr = [self parseJsonData:downloadedNSData]; //turns NSData into JSON array
                     //using NSJSONSerialization
NSMutableArray __block *first = [[NSMutableArray alloc]init];
NSMutableArray __block *second = [[NSMutableArray alloc]init];

//put half of arr in first and half in second with a loop

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dipatch_group_create(); 
dispatch_group_async(group, queue, ^
   {
      for (id element in first)
      {
         [MyDataClass parseData:element]; //create NSManagedObject subclass, save
      }
      [self saveContext];
   });

    dispatch_group_async(group, queue, ^
   {
      for (id element in second)
      {
         [MyDataClass parseData:element]; //create NSManagedObject subclass, save
      }
      [self saveContext];
   });

  dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

[self saveContext] calls save on the MOC. This is of course done on multiple threads, so I need to create a separate ManagedObjectContext for each thread. This is accomplished by exposing the MOC via a property on this object (self) that maintains an NSMutableDictionary of thread names (call description on NSThread) to NSManagedObjectContexts. When accessed, if there isn't a MOC for [NSThread currentThread], it creates a new one, adds it to the dictionary, and stores it. When each MOC is created, I subscribe to its change notifications:

NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(mergeChanges:)
      name:NSManagedObjectContextDidSaveNotification object:createdMOC];

In mergeChanges, I loop through my dictionary of contexts and call mergeChangesFromContextDidSaveNotification on all of them except the one for the thread that this is happening on. More specifically, I use [performSelector:onThread:withObject:waitUntilDone:] to have each MOC do this on the thread it's created for.

I'm also locking using NSLock around both the [myMOC save] and my mergeChanges method. If I didn't lock around those, I got "Cocoa error 133020", which apparently means an error merging changes.

So, this is what my logging tells me is happening:

  • Thread 1 acquires the lock to save and begins saving
  • A merge context notification comes on thread 1. Thread 1 acquires the lock for merging changes and begins that process. It merges changes for the MOC for the main thread and then hangs when doing one of the MOCs for the background threads.
  • Thread 2 starts to save but never acquires the lock for saving, because the other thread is stuck trying to merge changes.

So, why is it hanging when merging changes? Is there a better way to handle this scenario?

Update: I have tried using [persistentStoreCoordinator lock] around my [MOC save] call instead of just a lock with NSLock, to no avail. I also tried adding [NSThread sleepForTimeInterval:2] before the call to [self saveContext] in one of the dispatch_group_async calls, and it didn't help.

Update 2: Perhaps the better question here is why I was getting merge conflicts (Cocoa Error 133020). Is that expected? Am I doing the merge right (merging to all contexts except the one saving)?

Update 3: I've posted another question to address the larger context of how I'm doing the multithreading.

回答1:

When you're creating an NSManagedObject subclass, you're inserted it into a context and therefore you can only do that on the queue / thread that belongs to the context.

The same goes for calling -save on the context.

It seems to me from your code that you're inserting two objects — each on their own thread. That will not work.



回答2:

I've posted this question documenting my situation a little better. This current question I think is a little narrow in scope.