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() ,因为可以对其进行优化以在可能的情况下在当前线程上调用该块。