Multithreading 如何在跨线程合并更改时防止争用情况?
典型的设置:我们有一个带有Multithreading 如何在跨线程合并更改时防止争用情况?,multithreading,core-data,merge,race-condition,Multithreading,Core Data,Merge,Race Condition,典型的设置:我们有一个带有mainMOC的主线程和一个带有自己backgroundMOC的后台线程。后台线程通过将块分配到backgroundQueue对backgroundMOC执行只读操作 backgroundMOC需要合并来自mainMOC的更改,因此我们注册了nsmanagedobjectcontextdidsaveinnotification,然后执行类似操作 - (void)mainMocDidSave:(NSNotification *)notification { dis
mainMOC
的主线程和一个带有自己backgroundMOC
的后台线程。后台线程通过将块分配到backgroundQueue
对backgroundMOC
执行只读操作
backgroundMOC
需要合并来自mainMOC
的更改,因此我们注册了nsmanagedobjectcontextdidsaveinnotification
,然后执行类似操作
- (void)mainMocDidSave:(NSNotification *)notification {
dispatch_async(backgroundQueue, ^{
[backgroundMoc mergeChangesFromContextDidSaveNotification:notification];
});
}
假设用户删除了mainMOC
中的一个对象。上面的代码对我来说并不安全,因为合并将在将来的某个时候完成。在合并完成之前,backgroundQueue
上可能仍有块试图使用已删除的对象
显而易见的解决方案是使用调度同步
(或者执行锁定和等待
,执行选择器:OnThread:…
)。从我在互联网上看到的代码片段来看,这似乎是每个人都在做的事情。但我对这个解决方案也不满意
名称NSManagedObjectContextDidSaveNotification
表示在发送通知时已进行保存。因此,相应的行已经从基础数据库中删除(假设是sqlite存储)dispatch\u sync
必须等待队列上的其他块完成,然后才能合并更改,而这些其他块仍可以尝试使用已删除的对象,从而导致出现NSObjectInaccessibleException
在我看来,将更改从一个线程/队列合并到另一个线程/队列的正确方法是
NSManagedObjectContextWillSaveNotification
和NSManagedObjectContextDidSaveNotification
李>
NSManagedObjectContextWillSaveNotification
上:清空backgroundQueue
并挂起向队列分配新块的任何操作这是正确的方法还是我遗漏了什么?我在两个项目中使用了以下结构,其中我遇到了与您类似的问题。首先,我使用singleton服务来确保只有一个后台线程合并和读取更改 AppDelegate.m
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
// It is crucial to use the correct concurrency type!
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
- (void)saveContext {
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
else {
[[NSNotificationCenter defaultCenter] postNotificationName:@"ParentContextDidSaveNotification" object:nil];
}
}
}
- (id)init {
self = [super init];
if (self) {
[self managedObjectContext];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(parentContextDidSave) name:@"ParentContextDidSaveNotification" object:nil];
}
return self;
}
- (NSManagedObjectContext *)managedObjectContext {
if (!_managedObjectContext) {
// Again, make sure you use the correct concurrency type!
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_managedObjectContext setParentContext:[(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext]];
}
return _managedObjectContext;
}
- (BOOL)saveContext {
@synchronized(self) {
BOOL successful = YES;
// Bad practice, process errors appropriately.
[[self managedObjectContext] save:nil];
[[[self managedObjectContext] parentContext] performBlock:^{
[(AppDelegate *)[[UIApplication sharedApplication] delegate] saveContext];
}];
return successful;
}
}
- (void)parentContextDidSave {
[[self managedObjectContext] reset];
[[NSNotificationCenter defaultCenter] postNotificationName:@"ManagedObjectContextResetNotification" object:nil];
}
BackgroundService.m
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
// It is crucial to use the correct concurrency type!
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
- (void)saveContext {
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
else {
[[NSNotificationCenter defaultCenter] postNotificationName:@"ParentContextDidSaveNotification" object:nil];
}
}
}
- (id)init {
self = [super init];
if (self) {
[self managedObjectContext];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(parentContextDidSave) name:@"ParentContextDidSaveNotification" object:nil];
}
return self;
}
- (NSManagedObjectContext *)managedObjectContext {
if (!_managedObjectContext) {
// Again, make sure you use the correct concurrency type!
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_managedObjectContext setParentContext:[(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext]];
}
return _managedObjectContext;
}
- (BOOL)saveContext {
@synchronized(self) {
BOOL successful = YES;
// Bad practice, process errors appropriately.
[[self managedObjectContext] save:nil];
[[[self managedObjectContext] parentContext] performBlock:^{
[(AppDelegate *)[[UIApplication sharedApplication] delegate] saveContext];
}];
return successful;
}
}
- (void)parentContextDidSave {
[[self managedObjectContext] reset];
[[NSNotificationCenter defaultCenter] postNotificationName:@"ManagedObjectContextResetNotification" object:nil];
}
作为跟进:我一直在用上面的解决方案陷入僵局。在某些情况下,当主线程发送
NSManagedObjectContextWillSaveNotification
时,PSC上的锁似乎已经被主线程获取,这可能会导致上面步骤2中所需的调度同步调用在后台队列上仍有未完成的任务时永远等待。