First, i have a weak property. It points to the thread that is not main thread.
#property (nonatomic, weak) id weakThread;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
{
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:#selector(threadRun) object:nil];
self.weakThread = thread;
[thread start];
}
NSLog(#"main: %#, %p", self.weakThread, self.weakThread);
return YES;
}
- (void)threadRun {
NSLog(#"current: %#, %p", [NSThread currentThread], [NSThread currentThread]);
NSLog(#"self.weakThread in thread: %#, %p", self.weakThread, self.weakThread);
}
Look at these code. After i run, this is output:
main: <NSThread: 0x608000278240>{number = 5, name = main}, 0x608000278240
current: <NSThread: 0x608000278240>{number = 5, name = (null)}, 0x608000278240
self.weakThread in thread: <NSThread: 0x608000278240>{number = 5, name = (null)}, 0x608000278240
the pointer is never changed. But the thread is changed. I don't know why it is changed to main thread.
You see the first output, name is main.
Actually, the self.weakThread and [NSThread currentThread] in your code are the same, so the pointer doesn't need to be changed. It did not change to the main thread(the name 'main' is fake). You can prove it by assigning a name to thread:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:#selector(threadRun) object:nil];
thread.name = #"a thread";
The result will be changed to
"{number = 5, name = a thread}".
And you will find out that the real main thread has the different address by:
NSLog(#"real main: %#", [NSThread mainThread]);
NSLog(#"my thread: %#", self.weakThread);
Related
Managed object context initialised with NSMainQueueConcurrencyType and NSPrivateQueueConcurrencyType ties to main queue and private queue which are serial queues where operations are executed in FIFO order.
With below sample code:
NSLog(#"Current thread : %#", [NSThread currentThread]);
[mainMoc performBlock:^{
NSLog(#"main 1 - %#", [NSThread currentThread]);
}];
[mainMoc performBlockAndWait:^{
NSLog(#"main 2 - %#", [NSThread currentThread]);
}];
[mainMoc performBlock:^{
NSLog(#"main 3 - %#", [NSThread currentThread]);
}];
[bgMoc performBlock:^{
NSLog(#"bg 1 - %#", [NSThread currentThread]);
}];
[bgMoc performBlockAndWait:^{
NSLog(#"bg 2 - %#", [NSThread currentThread]);
}];
[bgMoc performBlock:^{
NSLog(#"bg 3 - %#", [NSThread currentThread]);
}];
I was expecting it to print
main 1, main 2 and main 3 like bg 1, bg 2 and bg 3 in serial order but instead this was printed:
Current thread : <NSThread: 0x60000006fb80>{number = 1, name = main}
main 2 - <NSThread: 0x60000006fb80>{number = 1, name = main}
bg 1 - <NSThread: 0x600000268900>{number = 3, name = (null)}
bg 2 - <NSThread: 0x60000006fb80>{number = 1, name = main}
bg 3 - <NSThread: 0x600000268900>{number = 3, name = (null)}
main 1 - <NSThread: 0x60000006fb80>{number = 1, name = main}
main 3 - <NSThread: 0x60000006fb80>{number = 1, name = main}
What could be the theory behind it given both main and private queue are serial?
Concurrency is non-deterministic. The only thing you're guaranteed is that "main1" is being executed before "main3", because it is, as you said, a FIFO queue.
It is important to differentiate between performBlock and performBlockAndWait.
performBlock is asynchronous, so it just puts the block into the queue and returns immediately. Those blocks will be executed in order. Thats why "main1" will always be executed before "main3".
performBlockAndWait is synchronous and thus can't take care of the queue by definition. This means it will execute the block right now and it won't return until it's done.
If it didn't do this, it would block, because the queue isn't empty or it would have to execute all the other tasks in the queue first.
Now the reason for why "bg1" comes before "bg2" is scheduling. I'm almost certain, that if you execute this test multiple times, it might eventually be different.
If the main-thread would be faster to reach the synchronous "bg2" it would appear first.
I am using the following code to prevent a background task from being started twice. Why does this code block?
- (void)doBackgroundTask {
NSLog(#"Before LOCK: %#", [NSThread currentThread]);
// lockObject is created once in init
#synchronized(lockObject) {
if (taskActive)
taskCanceller.shouldCancel = true;
while (taskActive) {
// NOOP: Busy waiting
//NSLog(#"Busy waiting: %#", [NSThread currentThread]);
}
taskActive = true;
taskCanceller = [[AsyncTaskCanceller alloc] init];
}
NSLog(#"After LOCK: %#", [NSThread currentThread]);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"AsyncStart: %#", [NSThread currentThread]);
AsyncTaskCanceller *canceller = taskCanceller;
[self loadDataAsync:canceller];
NSLog(#"Setting taskActive to FALSE: %#", [NSThread currentThread]);
taskActive = false;
NSLog(#"taskActive == FALSE: %#", [NSThread currentThread]);
if (canceller.shouldCancel == false) {
NSLog(#"Complete: %#", [NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Completed: %#", [NSThread currentThread]);
...
});
} else {
NSLog(#"Canceled: %#", [NSThread currentThread]);
}
NSLog(#"AsyncComplete: %#", [NSThread currentThread]);
});
NSLog(#"SyncComplete: %#", [NSThread currentThread]);
}
The code produces the following output:
Before LOCK: <NSThread: 0x1706663c0>{number = 5, name = (null)}
After LOCK: <NSThread: 0x1706663c0>{number = 5, name = (null)}
SyncComplete: <NSThread: 0x1706663c0>{number = 5, name = (null)}
AsyncStart: <NSThread: 0x174c75d00>{number = 7, name = (null)}
Before LOCK: <NSThread: 0x1706663c0>{number = 5, name = (null)}
Setting taskActive to FALSE: <NSThread: 0x174c75d00>{number = 7, name = (null)}
taskActive == FALSE: <NSThread: 0x174c75d00>{number = 7, name = (null)}
Canceled: <NSThread: 0x174c75d00>{number = 7, name = (null)}
AsyncComplete: <NSThread: 0x174c75d00>{number = 7, name = (null)}
Which can be translated to:
Thread 5 enters the method
Thread 5 gets the LOCK and check if the Task is running. This is not the case, so it is started in a background thread
Thread 5 exits the method
Task is performed in Thread 7
Thread 5 enters the method again and gets the LOCK. Since Task is running, it switches to busy waiting until taskActive is set to false
Thread 7 sets taskActive = false
Thread 7 finishes
After Thread 7 set taskActiveback to false the while loop should end, shouldn't it? Why does it not?
Even more strange: If I active the NSLog statement within the busy waiting while loop everything works as expected.
I get some data from a couple of web services that are called asynchronously. When I receive their responses, I need to create and save corresponding entities in Core Data with the information received. Since the services callbacks ara asynchronous, and I could be already saving the response of one of the services when I receive the another, I wrote a couple of methods like this:
- (void)createEntity
{
#autoreleasepool {
dispatch_queue_t queue = dispatch_queue_create(kSaveQueue, NULL);
dispatch_async(queue, ^{
// Context for background operations
NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] init];
NSPersistentStoreCoordinator *mainThreadContextPSC = [self.context persistentStoreCoordinator];
[tmpContext setPersistentStoreCoordinator:mainThreadContextPSC];
#try {
// Parse service response and create entity
// Save context
[tmpContext save:nil];
dispatch_async(dispatch_get_main_queue(), ^{
// Notify end of operation
});
}
#catch (NSException *ex) {
NSLog(#"exception: %#", [ex description]);
}
});
}
}
Actually, I have two methods like this, one for let's say EntityA, and another for EntityB, and each one is called when I receive the corresponding service response (serviceA, serviceB). In my tests I see that both tmpContext are always saved in iOS 8, but in iOS 7 it is only the first called which is saved, and the second entity is not persisted in Core Data.
Why does this work in iOS 8 but it doesn't in iOS 7?
Thanks in advance
Your approach to create context with alloc init and then assign the persistent store coordinator is deprecated.
Instead, use the factory method initWithConcurrencyType: and pass NSPrivateQueueConcurrencyType for a background thread. Associate with the parent context by calling setParentContext:.
You can also do background operations by taking advantage of the context's performBlock and performBlockAndWait APIs rather than dropping down to GCD.
Above answer from Mundi is right and good explanations.. I can give you the code I use to create a thread context and save and stop context
+ (NSManagedObjectContext*)startThreadContext {
AppDelegate *theDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = theDelegate.managedObjectContext;
NSThread *thread = [NSThread currentThread];
if ([thread isMainThread]) {
return moc;
}
// get thread dictionary
NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
if ( [threadDictionary objectForKey:#"managedObjectContext"] == nil ) {
// create a context for this thread
NSManagedObjectContext *newMoc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[newMoc setPersistentStoreCoordinator:[theDelegate persistentStoreCoordinator]];
// Register for context save changes notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:newMoc];
[newMoc setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[newMoc processPendingChanges]; // flush operations for which you want undos
[[newMoc undoManager] disableUndoRegistration];
newMoc.undoManager = nil;
// cache the context for this thread
[threadDictionary setObject:newMoc forKey:#"managedObjectContext"];
}
return [threadDictionary objectForKey:#"managedObjectContext"];
}
+ (void)saveAndStopThreadContext:(NSManagedObjectContext *)context {
// save managed object
NSError* error = nil;
BOOL success = [context save:&error];
if ( !success ) {
ERRLOG(#"[stopThreadContext] failed to save managedObjectContext (err:%#)", error );
}
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:context];
NSThread *thread = [NSThread currentThread];
if (![thread isMainThread]) {
NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
[threadDictionary removeObjectForKey:#"managedObjectContext"];
}
}
And you can use it like this
// get managed object context
NSManagedObjectContext* moc = [CoreDataHelper startThreadContext];
// perform update
[moc performBlock:^{
/*
Do something...
*/
// save and stop thread context
[CoreDataHelper saveAndStopThreadContext:moc];
}];
I'm experiencing the same deadlock issue (that is quite common on SO) that occurs in the multiple NSManagedObjectContexts & multiple threads scenario. In some of my view controllers, my app uses background threads to get data from a web service, and in that same thread it saves it. In others, where it makes sense to not progress any further without saving (e.g. persist values from a form when they hit "Next"), the save is done on the main thread. AFAIK there should be nothing wrong with this in theory, but occasionally I can make the deadlock happen on a call to
if (![moc save:&error])
...and this seems to be always on the background thread's save when the deadlock occurs. It doesn't happen on every call; in fact it's quite the opposite, I have to use my app for a couple of minutes and then it'll happen.
I've read all the posts I could find as well as the Apple docs etc, and I'm sure I'm following the recommendations. To be specific, my understanding of working with multiple MOCs/threads boils down to:
Each thread must have its own MOC.
A thread's MOC must be created on that thread (not passed from one thread to another).
A NSManagedObject cannot be passed, but a NSManagedObjectID can, and you use the ID to inflate a NSManagedObject using a different MOC.
Changes from one MOC must be merged to another if they are both using the same PersistentStoreCoordinator.
A while back I came across some code for a MOC helper class on this SO thread and found that it was easily understandable and quite convenient to use, so all my MOC interaction is now thru that. Here is my ManagedObjectContextHelper class in its entirety:
#import "ManagedObjectContextHelper.h"
#implementation ManagedObjectContextHelper
+(void)initialize {
[[NSNotificationCenter defaultCenter] addObserver:[self class]
selector:#selector(threadExit:)
name:NSThreadWillExitNotification
object:nil];
}
+(void)threadExit:(NSNotification *)aNotification {
TDAppDelegate *delegate = (TDAppDelegate *)[[UIApplication sharedApplication] delegate];
NSString *threadKey = [NSString stringWithFormat:#"%p", [NSThread currentThread]];
NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts;
[managedObjectContexts removeObjectForKey:threadKey];
}
+(NSManagedObjectContext *)managedObjectContext {
TDAppDelegate *delegate = (TDAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = delegate.managedObjectContext;
NSThread *thread = [NSThread currentThread];
if ([thread isMainThread]) {
[moc setMergePolicy:NSErrorMergePolicy];
return moc;
}
// a key to cache the context for the given thread
NSString *threadKey = [NSString stringWithFormat:#"%p", thread];
// delegate.managedObjectContexts is a mutable dictionary in the app delegate
NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts;
if ( [managedObjectContexts objectForKey:threadKey] == nil ) {
// create a context for this thread
NSManagedObjectContext *threadContext = [[NSManagedObjectContext alloc] init];
[threadContext setPersistentStoreCoordinator:[moc persistentStoreCoordinator]];
[threadContext setMergePolicy:NSErrorMergePolicy];
// cache the context for this thread
NSLog(#"Adding a new thread:%#", threadKey);
[managedObjectContexts setObject:threadContext forKey:threadKey];
}
return [managedObjectContexts objectForKey:threadKey];
}
+(void)commit {
// get the moc for this thread
NSManagedObjectContext *moc = [self managedObjectContext];
NSThread *thread = [NSThread currentThread];
if ([thread isMainThread] == NO) {
// only observe notifications other than the main thread
[[NSNotificationCenter defaultCenter] addObserver:[self class] selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:moc];
}
NSError *error;
if (![moc save:&error]) {
NSLog(#"Failure is happening on %# thread",[thread isMainThread]?#"main":#"other");
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0) {
for(NSError* detailedError in detailedErrors) {
NSLog(#" DetailedError: %#", [detailedError userInfo]);
}
}
NSLog(#" %#", [error userInfo]);
}
if ([thread isMainThread] == NO) {
[[NSNotificationCenter defaultCenter] removeObserver:[self class] name:NSManagedObjectContextDidSaveNotification
object:moc];
}
}
+(void)contextDidSave:(NSNotification*)saveNotification {
TDAppDelegate *delegate = (TDAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = delegate.managedObjectContext;
[moc performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:saveNotification
waitUntilDone:NO];
}
#end
Here's a snippet of the multi-threaded bit where it seems to deadlock:
NSManagedObjectID *parentObjectID = [parent objectID];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
// GET BACKGROUND MOC
NSManagedObjectContext *backgroundContext = [ManagedObjectContextHelper managedObjectContext];
Parent *backgroundParent = (Parent*)[backgroundContext objectWithID:parentObjectID];
// HIT THE WEBSERVICE AND PUT THE RESULTS IN THE PARENT OBJECT AND ITS CHILDREN, THEN SAVE...
[ManagedObjectContextHelper commit];
dispatch_sync(dispatch_get_main_queue(), ^{
NSManagedObjectContext *mainManagedObjectContext = [ManagedObjectContextHelper managedObjectContext];
parent = (Parent*)[mainManagedObjectContext objectWithID:parentObjectID];
});
});
The conflictList in the error seems to suggest that it's something to do with the ObjectID of the parent object:
conflictList = (
"NSMergeConflict (0x856b130) for NSManagedObject (0x93a60e0) with objectID '0xb07a6c0 <x-coredata://B7371EA1-2532-4D2B-8F3A-E09B56CC04F3/Child/p4>'
with oldVersion = 21 and newVersion = 22
and old object snapshot = {\n parent = \"0xb192280 <x-coredata://B7371EA1-2532-4D2B-8F3A-E09B56CC04F3/Parent/p3>\";\n name = \"New Child\";\n returnedChildId = 337046373;\n time = 38;\n}
and new cached row = {\n parent = \"0x856b000 <x-coredata://B7371EA1-2532-4D2B-8F3A-E09B56CC04F3/Parent/p3>\";\n name = \"New Child\";\n returnedChildId = 337046373;\n time = 38;\n}"
);
I've tried putting in refreshObject calls as soon as I've gotten hold of a MOC, with the theory being that if this is a MOC we've used before (e.g. we used an MOC on the main thread before and it's likely that this is the same one that the helper class will give us), then perhaps a save in another thread means that we need to explicitly refresh. But it didn't make any difference, it still deadlocks if I keep clicking long enough.
Does anyone have any ideas?
Edit: If I have a breakpoint set for All Exceptions, then the debugger pauses automatically on the if (![moc save:&error]) line, so the play/pause button is already paused and is showing the play triangle. If I disable the breakpoint for All Exceptions, then it actually logs the conflict and continues - probably because the merge policy is currently set to NSErrorMergePolicy - so I don't think it's actually deadlocking on the threads. Here's a screehshot of the state of both threads while it's paused.
I do not recommend your approach at all. First, unless you are confined to iOS4, you should be using the MOC concurrency type, and not the old method. Even under iOS 5 (which is broken for nested contexts) the performBlock approach is much more sound.
Also, note that dispatch_get_global_queue provides a concurrent queue, which can not be used for synchronization.
The details are found here: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html
Edit
You are trying to manage MOCs and threading manually. You can do it if you want, but there be dragons in your path. That's why the new way was created, to minimize the chance for errors in using Core Data across multiple threads. Anytime I see manual thread management with Core Data, I will always suggest to change as the first approach. That will get rid of most errors immediately.
I don't need to see much more than you manually mapping MOCs and threads to know that you are asking for trouble. Just re-read that documentation, and do it the right way (using performBlock).
Very simple code:
queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
NSLog(#"%#", [NSThread mainThread]? #"main" : #"not main");
}];
prints "main".
Why? Isn't it suppose to run in bg thread asynchronously, unless i call [NSOperationQueue mainQueue] ?
[NSThread mainThread] always returns an object (and thus yielding YES when cast to BOOL), since there is a main thread when your program is running.
If you want to check whether the current thread is the main thread or not, you need to use the currentThread method of NSThread.
NSLog(#"%#", [[NSThread currentThread] isEqual:[NSThread mainThread]]
? #"main" : #"not main");
NSThread has a better method; it seems you can use the isMainThread method to check whether the current thread is the main thread or not:
if ([[NSThread currentThread] isMainThread]) {
//
}
Well as user #borrrden pointed out, you just need to use [NSThread isMainThread],
if([NSThread isMainThread]){
//
}
See the NSThread documentation.