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

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.

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

Concurrency on NSMainQueueConcurrencyType and NSPrivateQueueConcurrencyType contexts?

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.

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

dispatch_semaphore_wait does not wait on semaphore

I have developed the following method, which checks the app's ability to communicate with the server.
The method performs a simple query and knows that if it gets a result, the app should be connected (basic ping mechanism).
- (BOOL)isAppConnected
{
__block BOOL isConnected = NO;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[[SFRestAPI sharedInstance] performSOQLQuery:#"SELECT id FROM Account LIMIT 1"
failBlock:^(NSError *e) {
isConnected = NO;
NSLog(#"NOT CONNECTED %#", e);
NSLog(#"fail block ON THE MAIN THREAD? %hhd", [NSThread isMainThread]);
dispatch_semaphore_signal(semaphore);
} completeBlock:^(NSDictionary *dict) {
isConnected = YES;
NSLog(#"%#", dict);
NSLog(#"complete block ON THE MAIN THREAD? %hhd", [NSThread isMainThread]);
dispatch_semaphore_signal(semaphore);
}];
// if the wait times-out we will receive a non-zero result and can assume no connection to SF
//When using: DISPATCH_TIME_FOREVER the app hangs forever!!
int waitResult = dispatch_semaphore_wait(semaphore, 30 * NSEC_PER_SEC);
NSLog(#"waitResult: %d", waitResult);
return isConnected;
}
I am using the 'dispatch_semaphore_wait' as suggested in the Apple documentation
My goal is to wait on the response or a short timeout to figure out if we really have a valid connection.
With the code above, 'dispatch_semaphore_wait' never actually waits, i.e. execution does not stop at that line but it continues immediately (always returning 49 as the result to the dispatch_semaphore_wait call). That is unless I use DISPATCH_TIME_FOREVER in which case the app hangs forever...
At the moment I am calling this method from the main thread. I am aware that this is a bad idea, but I wanted to see this working as expected before refactoring.
What could be causing this behaviour?
Thanks.
The parameter for dispatch_semaphore_wait is not a delay, but the time when the semaphore should wake up. Yours will wake up 30 seconds after Midnight, Jan 1st. 1970 (or 2001, not sure). Use the dispatch_time function.
- (BOOL)isAppConnected
{
__block BOOL isConnected = NO;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// Add this code...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[SFRestAPI sharedInstance] performSOQLQuery:#"SELECT id FROM Account LIMIT 1"
failBlock:^(NSError *e) {
isConnected = NO;
NSLog(#"NOT CONNECTED %#", e);
NSLog(#"fail block ON THE MAIN THREAD? %hhd", [NSThread isMainThread]);
dispatch_semaphore_signal(semaphore);
} completeBlock:^(NSDictionary *dict) {
isConnected = YES;
NSLog(#"%#", dict);
NSLog(#"complete block ON THE MAIN THREAD? %hhd", [NSThread isMainThread]);
dispatch_semaphore_signal(semaphore);
}];
});
int waitResult = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(#"waitResult: %d", waitResult);
return isConnected;
}
First of all if your fail and complete block are invoked on the main thread then you are going to wait forever, or until the timeout you specify 'times out'
The reason is because the main thread starts waiting after you call dispatch_semaphore_wait. And then if your performSOQLQuery calls the blocks on the main thread nothing will happen until the time out 'times out'.
Now of coarse if you specify a time out of forever the semaphore will never signal or let go, which means your main thread will wait forever, for itself.
Change the waiting code to this: never make the main thread wait
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
int waitResult = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(#"waitResult: %d", waitResult);
});
And do not return a Bool as you are doing, since this is a lengthy operation, you want to use a block that has a result.
Also make sure that your performSOQLQuery() method is not on the main thread please.

Where is the GCD mechanism invoked?

I follow RAY WENDERLICH GCD tutorial- part 2, and I don't get this:
First implementation
- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ // 1
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create(); // 2
for (NSInteger i = 0; i < 3; i++) {
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
dispatch_group_enter(downloadGroup); // 3
Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
dispatch_group_leave(downloadGroup); // 4
}];
[[PhotoManager sharedManager] addPhoto:photo];
}
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); // 5
dispatch_async(dispatch_get_main_queue(), ^{ // 6
if (completionBlock) { // 7
completionBlock(error);
}
});
});
}
Second Implementation:
- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
// 1
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create();
for (NSInteger i = 0; i < 3; i++) {
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
dispatch_group_enter(downloadGroup); // 2
Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
dispatch_group_leave(downloadGroup); // 3
}];
[[PhotoManager sharedManager] addPhoto:photo];
}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ // 4
if (completionBlock) {
completionBlock(error);
}
});
}
I the first implementation the relevant code is surrounded with dispatch_async and everything is super clear.
BUT, the second implementation is unclear! I don't get it, how the GCD mechanism is taking any part beside of the notification for enter and leave?
The first implementation runs a background thread starting at
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ // 1
So the whole function runs there. But this background thread gets eventually blocked at this line (it waits for all the photos to be downloaded):
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER);
This is all fine since you are not blocking the main thread. But it is a bit of a clumsy implementation since,
you block a thread where you really have not to and
the code looks a bit ugly with the dispatch_async around the whole function.
So the second implementation has two benefits:
It's not that "ugly" and a bit more readable (because you get rid of the dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{})
and there is no block, because dispatch_group_notify serves as the asynchronous completion block.
The first method is an asynchronous method. You can call it from any thread, including the main thread. It will perform actions in a background thread, and when it's done, it will dispatch the callback block on the main thread. A call to this method returns immediately, the callback will be called a lot later.
The second method is a blocking method. The method will not finish until the photos are downloaded, and then it calls your callback method. This method should most definitely not be called from the main thread, only from a background thread.

Resources