multithreading - How to prevent race conditions when merging changes across threads? -
a typical setup: have main thread mainmoc
, background thread own backgroundmoc
. background thread performs read-only operations on backgroundmoc
dispatching blocks backgroundqueue
.
the backgroundmoc
needs merge changes mainmoc
register nsmanagedobjectcontextdidsavenotification
, like
- (void)mainmocdidsave:(nsnotification *)notification { dispatch_async(backgroundqueue, ^{ [backgroundmoc mergechangesfromcontextdidsavenotification:notification]; }); }
let's user deletes object in mainmoc
. code above not seem safe me, since merge done @ point in future. until merge done, there might still blocks on backgroundqueue
trying use deleted object.
the obvious solution use dispatch_sync
instead (or performblockandwait
, performselector:onthread:...
) instead. code snippets see on interwebs, seems doing. i'm not comfortable solution either.
the name nsmanagedobjectcontextdidsavenotification
implies save has happened when notification delivered. corresponding row has been deleted underlying database (assuming sqlite store). dispatch_sync
have wait other blocks on queue finish before can merge changes, , these other blocks still try work deleted object, leading nsobjectinaccessibleexception
.
it seems me correct way merge changes 1 thread/queue to
- subscribe
nsmanagedobjectcontextwillsavenotification
,nsmanagedobjectcontextdidsavenotification
on background thread. - on
nsmanagedobjectcontextwillsavenotification
: emptybackgroundqueue
, suspend operations dispatch new blocks queue. - on
nsmanagedobjectcontextdidsavenotification
: merge changes synchronously. - resume normal operation on background queue.
is correct approach or missing something?
i use following structure in 2 projects in experienced similar troubles did. first of use singleton service ensure there 1 background thread merging , reading changes.
appdelegate.m
- (nsmanagedobjectcontext *)managedobjectcontext { if (_managedobjectcontext != nil) { return _managedobjectcontext; } nspersistentstorecoordinator *coordinator = [self persistentstorecoordinator]; if (coordinator != nil) { // crucial use 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 implementation code handle error appropriately. // abort() causes application generate crash log , terminate. should not use function in shipping application, although may useful during development. nslog(@"unresolved error %@, %@", error, [error userinfo]); abort(); } else { [[nsnotificationcenter defaultcenter] postnotificationname:@"parentcontextdidsavenotification" object:nil]; } } }
backgroundservice.m
- (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 use 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]; }
Comments
Post a Comment