核心数据并发 `performBlockAndWait:` NSManagedObjectContext zombie
Core Data concurrency `performBlockAndWait:` NSManagedObjectContext zombie
我发布的应用程序有以下崩溃报告:
synchronizeMyWords
方法从数据库中获取实体,创建具有主上下文父级的私有队列上下文,最后保存结果。所有操作都在后台线程中。每次应用程序进入 background
和 foreground
时都会调用此方法。这是一个简化的方法:
- (AWSTask *)synchronizeMyWords {
__weak typeof(self) weakSelf = self;
AWSContinuationBlock block = ^id _Nullable(AWSTask * _Nonnull task) {
if ([task.result isKindOfClass:[NSArray class]]) {
NSArray * records = (NSArray *)task.result;
NSManagedObjectContext * context = [NSManagedObjectContext MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]];
[context performBlockAndWait:^{
for (NSDictionary * info in records) {
[RDRWord MR_createEntityInContext:context];
}
[context save:nil];
}];
return [AWSTask taskWithResult:@YES];
}
return [AWSTask taskWithError:[NSError errorWithDomain:@"" code:404 userInfo:nil]];
};
AWSExecutor * executor = [AWSExecutor defaultExecutor];
return [[self loadLocalWords] continueWithExecutor:executor withBlock:block];
}
如您所见,我正在使用 Magical Record 第 3 方库来管理核心数据堆栈。这是创建私有队列上下文的方法:
+ (NSManagedObjectContext *) MR_contextWithParent:(NSManagedObjectContext *)parentContext
{
NSManagedObjectContext *context = [self MR_newPrivateQueueContext];
[context setParentContext:parentContext];
[context MR_obtainPermanentIDsBeforeSaving];
return context;
}
您可以在 github here 上查看整个 NSManagedObjectContext+MagicalRecord
类别。
performBlockAndWait:
中的 context
对象在脱离作用域之前如何释放?
我个人无法重现崩溃,但我的很多用户(iOS 8.1 - 10 台设备)都受到此问题的影响。
更新 1:
例如,这是关于 blog
的相同报告
Core Data 提供了充足的 API 来处理后台线程。这些也可以通过 Magical Record 访问。
看起来你创建了太多不必要的线程。我认为 AWSContinuationBlock
和 AWSExecutor
的就业不是一个好主意。 synchronizeMyWords
可以从后台线程调用。该块可能在后台线程上 运行。在块内,您创建一个链接到子上下文的新后台线程。目前尚不清楚 loadLocalWords
returns 或 continueWithExecutor:block:
如何处理线程。
数据保存也有问题。保存子上下文后不保存主上下文;大概这会在以后发生,但可能与其他一些操作有关,因此您的代码之前可以工作的事实可能更像是 "false positive"。
我的建议是简化线程代码。您应该将自己限制在 Core Data 块 API 上。
我将@Mundi 的回答标记为正确,因为他写了您应该遵循的一般方法。现在,我想在这里分享我是如何调试它的。
首先,我了解到,可以在 xcode 中打开调试并发断言。
您需要在启动时传递以下参数:
-com.apple.CoreData.ConcurrencyDebug 1
现在,在您的应用程序输出中,您应该会看到日志消息:
2016-12-12 01:58:31.665 your-app[4267:2180376] CoreData: annotation: Core Data multi-threading assertions enabled.
打开它后,我的应用程序在 synchronizeMyWords
方法中崩溃(老实说,不仅如此。想知道为什么 Apple 在调试模式下默认不包含并发断言?)。我检查了 AWSCore library 中的 defaultExecutor
并看到了这个:
+ (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;
}
根据他们的 if
声明,我的 continuationBlock
不能保证在 DISPATCH_QUEUE_PRIORITY_DEFAULT
队列上执行。因此,我创建了一个共享 dispatch_queue_t
队列,并结合 performBlockAndWait:
CoreData 方法调用其上的所有操作。结果,现在没有崩溃,我提交了新版本。如果我没有收到任何关于 context
僵尸的崩溃报告,我将更新此 post。
我发布的应用程序有以下崩溃报告:
synchronizeMyWords
方法从数据库中获取实体,创建具有主上下文父级的私有队列上下文,最后保存结果。所有操作都在后台线程中。每次应用程序进入 background
和 foreground
时都会调用此方法。这是一个简化的方法:
- (AWSTask *)synchronizeMyWords {
__weak typeof(self) weakSelf = self;
AWSContinuationBlock block = ^id _Nullable(AWSTask * _Nonnull task) {
if ([task.result isKindOfClass:[NSArray class]]) {
NSArray * records = (NSArray *)task.result;
NSManagedObjectContext * context = [NSManagedObjectContext MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]];
[context performBlockAndWait:^{
for (NSDictionary * info in records) {
[RDRWord MR_createEntityInContext:context];
}
[context save:nil];
}];
return [AWSTask taskWithResult:@YES];
}
return [AWSTask taskWithError:[NSError errorWithDomain:@"" code:404 userInfo:nil]];
};
AWSExecutor * executor = [AWSExecutor defaultExecutor];
return [[self loadLocalWords] continueWithExecutor:executor withBlock:block];
}
如您所见,我正在使用 Magical Record 第 3 方库来管理核心数据堆栈。这是创建私有队列上下文的方法:
+ (NSManagedObjectContext *) MR_contextWithParent:(NSManagedObjectContext *)parentContext
{
NSManagedObjectContext *context = [self MR_newPrivateQueueContext];
[context setParentContext:parentContext];
[context MR_obtainPermanentIDsBeforeSaving];
return context;
}
您可以在 github here 上查看整个 NSManagedObjectContext+MagicalRecord
类别。
performBlockAndWait:
中的 context
对象在脱离作用域之前如何释放?
我个人无法重现崩溃,但我的很多用户(iOS 8.1 - 10 台设备)都受到此问题的影响。
更新 1:
例如,这是关于 blog
的相同报告Core Data 提供了充足的 API 来处理后台线程。这些也可以通过 Magical Record 访问。
看起来你创建了太多不必要的线程。我认为 AWSContinuationBlock
和 AWSExecutor
的就业不是一个好主意。 synchronizeMyWords
可以从后台线程调用。该块可能在后台线程上 运行。在块内,您创建一个链接到子上下文的新后台线程。目前尚不清楚 loadLocalWords
returns 或 continueWithExecutor:block:
如何处理线程。
数据保存也有问题。保存子上下文后不保存主上下文;大概这会在以后发生,但可能与其他一些操作有关,因此您的代码之前可以工作的事实可能更像是 "false positive"。
我的建议是简化线程代码。您应该将自己限制在 Core Data 块 API 上。
我将@Mundi 的回答标记为正确,因为他写了您应该遵循的一般方法。现在,我想在这里分享我是如何调试它的。 首先,我了解到,可以在 xcode 中打开调试并发断言。 您需要在启动时传递以下参数:
-com.apple.CoreData.ConcurrencyDebug 1
现在,在您的应用程序输出中,您应该会看到日志消息:
2016-12-12 01:58:31.665 your-app[4267:2180376] CoreData: annotation: Core Data multi-threading assertions enabled.
打开它后,我的应用程序在 synchronizeMyWords
方法中崩溃(老实说,不仅如此。想知道为什么 Apple 在调试模式下默认不包含并发断言?)。我检查了 AWSCore library 中的 defaultExecutor
并看到了这个:
+ (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;
}
根据他们的 if
声明,我的 continuationBlock
不能保证在 DISPATCH_QUEUE_PRIORITY_DEFAULT
队列上执行。因此,我创建了一个共享 dispatch_queue_t
队列,并结合 performBlockAndWait:
CoreData 方法调用其上的所有操作。结果,现在没有崩溃,我提交了新版本。如果我没有收到任何关于 context
僵尸的崩溃报告,我将更新此 post。