dispatch_sync()有陷阱
每个文档:
作为一种优化,此函数在可能的情况下在当前线程上调用该块。
如果您尝试提供其他队列并在主队列上调用此函数,则这是一个陷阱,它实际上会将所有帧从主队列堆叠到目标队列中,如下所示:
dispatch_sync(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
[RestAPI registerDevice:^{
}];
});
致电之前:
块内:
请注意,属于主队列的帧现在堆叠在后台队列中。
当您尝试在块中添加信号量时,这会变得混乱,例如:
+ (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];
}
}
}
在这种情况下,尽管原始dispatch_sync()
内的信号量进行了怪异的“ sync”操作,该块仍将使用AFNetworking会话管理器来发布内容。
管理器内部有一个棘手的属性,称为completionQueue
队列,默认为主队列。
现在,当请求得到响应时,将调用AFNetworking:
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);
}
...
});
请注意,主队列已堆叠到后台队列中,尽管它说的是后台队列,但实际上是主队列本身。 因此,信号量实际上已锁定在主队列上。 当completionQueue
在此处为nil时,它将尝试获取主队列并执行该块,该块最终将变为死锁,因为我们的主队列被信号量阻塞,它将耗尽时间并一遍又一遍地重复。
如果我们在此处删除while循环,则信号量将超时,我们将不得不再睡眠REGISTER_RESEND_INTERVAL
秒,然后将在主队列上再次调用完成后处理程序。
总而言之,请谨慎使用dispatch_sync()
,因为可以对其进行优化以在可能的情况下在当前线程上调用该块。