Concurrency on NSMainQueueConcurrencyType and NSPrivateQueueConcurrencyType contexts? - ios

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.

Related

Why Dispatch_group_notify works different in different environment?

The first situation is that I create a Command Line Tool Application,and run this code.
NSLog(#"Main:%#", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(#"Task1:%#", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(#"Task2:%#", [NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(#"Finish:%#", [NSThread currentThread]);
});
The log in terminal is
Main:<NSThread: 0x1028033b0>{number = 1, name = main}
Task2:<NSThread: 0x10040f0f0>{number = 2, name = (null)}
Task1:<NSThread: 0x1006008d0>{number = 3, name = (null)}
If I want to show last log in queue and replace the main queue
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(#"Finish:%#", [NSThread currentThread]);
});
with queue
dispatch_group_notify(group, queue, ^{
NSLog(#"Finish:%#", [NSThread currentThread]);
});
The terminal print the last log.But why it can't revoke in Main queue?
When i copy this code to simple iOS Application.All works well:
Main:<NSThread: 0x600000070ac0>{number = 1, name = main}
Task2:<NSThread: 0x6000002633c0>{number = 3, name = (null)}
Task1:<NSThread: 0x600000263480>{number = 4, name = (null)}
MainFinish:<NSThread: 0x600000070ac0>{number = 1, name = main}
And I try to add sleep(1) over Task1 in 'Command Tool Line', but it seems block the queue and only print log:Task2.... But this all works well in simple iOS Application.
Why lead to these different?
image here
Unlike other queues that active on created,you should call dispatch_main() method to execute blocks submitted to main thread
image here
The reason for the same code runs well in iOS Application is that the application start a default runloop which execute the task submitted to the main queue according to a certain rule like UI update notification.
The reference as follow:
swift-corelibs-libdispatch
Concurrency Programming Guide

Objective-C multi threading - Why does my code produce a deadlock?

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.

Why a new thread points to main thread?

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);

GCD sync serial main_queue [duplicate]

This question already has answers here:
Why dispatch_sync( ) call on main queue is blocking the main queue?
(5 answers)
dispatch_sync() always execute block in main thread
(3 answers)
Closed 6 years ago.
-(void)test1{
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(#"start");
dispatch_sync(queue, ^{
NSLog(#"%#",[NSThread currentThread]);
});
}
-(void)test2{
dispatch_queue_t queue = dispatch_queue_create("com.yaoye.serial", DISPATCH_QUEUE_SERIAL);
NSLog(#"start");
dispatch_sync(queue, ^{
NSLog(#"%#",[NSThread currentThread]);
});
}
Test1 and test2 are executed in the main thread
Test1 example:
the main thread is blocked waiting for synchronization function, block into the main thread of the runloop cannot be executed, lead to deadlock.
Test2 example:
the main thread waiting for synchronization function is blocked,block into the main thread of the runloop, but no deadlock.<2016-03-14 13:55:06.730 GCD[54320:12111593] <NSThread: 0x7fef4ac08810>{number = 1, name = main}>
queation:
Why not is test2 deadlock?
because test1 uses the same main queue, whereas test2 uses a different queue

Saving Core Data on seperate thread with Magic Record

I am using Magical Record to help with core data saving and multi threading.
I kick off a new thread with GCD. In that new thread, I check if an Entity exists; if it does not I want to create a new one and save it.
Will saveUsingCurrentThreadContextWithBlock^(NSManagedObjectContext *localContext){} return to the main thread to save if it is called on a non-main thread?
or should i just pass the context to the new thread?
EDIT:
On the main thread, I create a MBProgress indicator and create a new thread:
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.mapView animated:YES];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
Person *person = [Person MR_findFirstByAttribute:NAME withValue:self.user.username];
if (person == NULL) {
NSLog(#"SEPERATE THREAD | person %# does not exist, creating", self.user.username);
person = [Person MR_createEntity];
person.name = self.user.username;
person.uid = self.user.UID;
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveOnlySelfWithCompletion:^(BOOL success, NSError *error) {
[MBProgressHUD hideHUDForView:self.mapView animated:YES];
Person *person = [Person MR_findFirstByAttribute:NAME withValue:self.user.username];
if (person) {
NSLog(#"COMPLETION BLOCK | person exists: %#", person.name);
}
}];
}
else {
NSLog(#"SEPERATE THREAD | person %# does", self.user.username);
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.mapView animated:YES];
});
}
});
(this method of saving is not persistence, I restart the app and I can't find the Person entity):
2013-03-12 14:25:44.014 SEPERATE THREAD | person iDealer does not exist, creating
2013-03-12 14:25:44.014 SEPERATE THREAD | thread: <NSThread: 0x84ca720>{name = (null), num = 4}
2013-03-12 14:25:44.015 -[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0x840ec30) → Saving <NSManagedObjectContext (0x840ec30): *** UNNAMED ***> on *** BACKGROUND THREAD ***
2013-03-12 14:25:44.015 -[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0x840ec30) → Save Parents? 0
2013-03-12 14:25:44.015 -[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0x840ec30) → Save Synchronously? 0
2013-03-12 14:25:44.016 -[NSManagedObjectContext(MagicalRecord) MR_contextWillSave:](0x840ec30) Context UNNAMED is about to save. Obtaining permanent IDs for new 1 inserted objects
2013-03-12 14:25:44.132 __70-[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:]_block_invoke21(0x840ec30) → Finished saving: <NSManagedObjectContext (0x840ec30): *** UNNAMED ***> on *** BACKGROUND THREAD ***
2013-03-12 14:25:44.134 COMPLETION BLOCK | thread: <NSThread: 0x8435f30>{name = (null), num = 1}
2013-03-12 14:25:44.134 COMPLETION BLOCK | person exists: iDealer
Ok, I got it to work:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
Person *person = [Person MR_findFirstByAttribute:NAME withValue:self.user.username];
if (person == NULL) {
[MagicalRecord saveUsingCurrentThreadContextWithBlock:^(NSManagedObjectContext *localContext){
Person *localPerson = [Person MR_createInContext:localContext];
localPerson.name = self.user.username;
} completion:^(BOOL success, NSError *error){
[MBProgressHUD hideHUDForView:self.mapView animated:YES];
}];
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.mapView animated:YES];
});
}
});
This works and save. I am unsure what casademora means by this method is incorrect. I am not able to determine what the difference between using this method to using saveOnlySelf:completion: method.
It seems like I was never able to save the context to the persistent store with the saveOnlySelf. If I created it with the code in my question, it would get placed in the context. If I did a search for the Person entity, I could find it. But once I terminated the app and restarted, that Person entity would not be there. It felt like I was saving or merging the thread context to the main/default context but that context was not being saved.
EDIT:
After some more playing around with MR, it seems that if any of the saveOnlySelf methods are used in a non-main thread, it will merge local context to the default context, but it does not save it to the persistent store. If you check the default context after completion, the new entity is indeed there. But once you terminate the app and re-run it, it isn't there.
To merge the context and save to the store, you need to call one of the saveToPersistentStoreAndWait type methods.
In the current version of MagicalRecord, yes, the completion block will return to the main thread. However, your method here is incorrect. There are now more explicit variants of the save method:
saveOnlySelf:completion:
saveToPersistentStore:completion:
Have a look at these methods in the current version. And as a reminder, make sure you only use managed objects from the localContext given to you. That working block can be run in any thread/queue, and you still want to use the proper thread management rules for core data in this case.
Yes, the completion block with execute in the main thread. For example:
NSOperationQueue *newQueue = [[NSOperationQueue alloc] init];
[newQueue addOperationWithBlock:^{
[MagicalRecord saveUsingCurrentThreadContextWithBlock:^(NSManagedObjectContext *localContext) {
// save something
} completion:^(BOOL success, NSError *error) {
// this will execute in the main thread
}];
}];

Resources