dispatch_sync() has trap
Per documentation:
As an optimization, this function invokes the block on the current thread when possible.
This is a trap if you try to feed a different queue and call this function upon main queue, it will actually stack all frames into the target queue from main queue, like this:
dispatch_sync(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
[RestAPI registerDevice:^{
}];
});
notice that the frames that belong to main queue now is stacked on the background queue. This gets messy when you try to add a semaphore inside the block, for example:
+ (void)registerDevice:(void (^)(void))completionBlock {
while (true){
__block BOOL success = NO;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[[AFHTTPSessionManager sharedInstance] POST:@"register"
parameters:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
success = YES;
DDLog(@"[REGISTER SUCCESS] %@", responseObject);
dispatch_semaphore_signal(semaphore);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
DDLog(@"[REGISTER FAILED] %@", error);
dispatch_semaphore_signal(semaphore);
}];
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 2 * MI_HTTP_TIMEOUT * NSEC_PER_SEC);
dispatch_semaphore_wait(semaphore, timeout);
if (success) {
completionBlock();
break;
} else {
[NSThread sleepForTimeInterval:REGISTER_RESEND_INTERVAL];
}
}
}
In this case, despite the weird “sync” operation by the semaphore inside the original dispatch_sync()
, the block will use AFNetworking session manager to post something.
There is a tricky property called completionQueue
inside the manager, which default is the main queue.
Now when the request gets its response, AFNetworking will be called upon:
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, serializationError);
}
...
});
Notice the main queue has been stacked onto the background queue, though it’s saying background queue, it’s actually the main queue itself. So the semaphore is actually locked on the main queue. When completionQueue
is nil here, it will try to get the main queue and execute the block, which will eventually become dead lock, as our main queue is blocked by the semaphore, it will run time out and repeat over and over again.
If we remove the while loop here, the semaphore will time out and we will have to sleep another REGISTER_RESEND_INTERVAL
seconds, and then the post completion handler will be called on main queue again.
To sum up, be careful with the dispatch_sync()
as it can be optimized to invoke the block on the current thread when possible.