Core Data concurrency `performBlockAndWait:` NSManagedObjectContext zombie

半腔热情 提交于 2019-12-02 13:13:40

Core Data provides ample APIs to deal with background threads. These are also accessible via Magical Record.

It looks as if you creating too many threads unnecessarily. I think that the employment of AWSContinuationBlock and AWSExecutor is not a good idea. synchronizeMyWords could be called from a background thread. The block might be run on a background thread. Inside the block you create a new background thread linked to the child context. It is not clear what loadLocalWords returns, or how continueWithExecutor:block: deals with threads.

There is also a problem with the saving of the data. The main context is not saved after the child context is saved; presumably this happens later, but perhaps in connection with some other operation, so that the fact that your code was working before is perhaps more of a "false positive".

My recommendation is to simplify the threading code. You should confine yourself to the Core Data block APIs.

I marked @Mundi answer as correct, because he wrote the general approach you should follow. Now, I want to share here how I debugged it. Firstly, I learned that, it is available to turn on debug concurrency assertion in xcode. You need to pass following argument on launch:

-com.apple.CoreData.ConcurrencyDebug 1

Now, in your application output, you should see log message:

2016-12-12 01:58:31.665 your-app[4267:2180376] CoreData: annotation: Core Data multi-threading assertions enabled.

Once I turned it on, my app crashed in synchronizeMyWords method (honestly, not only there. Wondering, why Apple does not include concurrency assertions by default in debug mode?). I checked what defaultExecutor is in AWSCore library and saw this:

+ (instancetype)defaultExecutor {
    static AWSExecutor *defaultExecutor = NULL;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        defaultExecutor = [self executorWithBlock:^void(void(^block)()) {
            // We prefer to run everything possible immediately, so that there is callstack information
            // when debugging. However, we don't want the stack to get too deep, so if the remaining stack space
            // is less than 10% of the total space, we dispatch to another GCD queue.
            size_t totalStackSize = 0;
            size_t remainingStackSize = remaining_stack_size(&totalStackSize);

            if (remainingStackSize < (totalStackSize / 10)) {
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block);
            } else {
                @autoreleasepool {
                    block();
                }
            }
        }];
    });
    return defaultExecutor;
}

According to their if statement, my continuationBlock was not guaranteed to be executed on DISPATCH_QUEUE_PRIORITY_DEFAULT queue. So, I created one shared dispatch_queue_t queue and call all operations on it combining with performBlockAndWait: CoreData method. As a result, there are no crashes now and I submitted new release. I will update this post, if I do not get any crash report with context zombie.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!