dispatch_sync
有两个作用:
- 将一个 block 放入队列中。
- 阻塞当前线程,直到该 block 运行完成。
由于主线程是串行队列(即只使用一个线程),如果你在主队列上运行以下语句:
dispatch_sync(dispatch_get_main_queue(), ^(){/*...*/});
以下事件将会发生:
dispatch_sync
将块队列到主队列。
dispatch_sync
阻塞主队列的线程,直到块执行完成。
dispatch_sync
会一直等待,因为块应该运行的线程被阻塞了。
理解此问题的关键在于,
dispatch_sync
不会执行块,它只是将它们排队。执行将会在未来的运行循环迭代中发生。
下面是一个相关方法的示例:
if (queueA == dispatch_get_current_queue()){
block();
} else {
dispatch_sync(queueA, block);
}
这样做没问题,但要注意它不能保护你免受涉及队列层级的复杂情况的影响。在这种情况下,当前队列可能与您尝试发送阻塞时先前被阻塞的队列不同。例如:
dispatch_sync(queueA, ^{
dispatch_sync(queueB, ^{
dispatch_sync(queueA, ^{
});
});
});
对于复杂情况,要在分派队列中读写键值数据:
dispatch_queue_t workerQ = dispatch_queue_create("com.meh.sometask", NULL);
dispatch_queue_t funnelQ = dispatch_queue_create("com.meh.funnel", NULL);
dispatch_set_target_queue(workerQ,funnelQ);
static int kKey;
CFStringRef tag = CFSTR("funnel");
dispatch_queue_set_specific(funnelQ,
&kKey,
(void*)tag,
(dispatch_function_t)CFRelease);
dispatch_sync(workerQ, ^{
CFStringRef tag = dispatch_get_specific(&kKey);
if (tag){
dispatch_sync(funnelQ, ^{
});
} else {
}
});
解释:
- 我创建了一个指向
funnelQ
队列的workerQ
队列。在真实代码中,如果您有几个“worker”队列,并且您想要一次性恢复/挂起所有队列(通过恢复/更新它们的目标funnelQ
队列来实现),那么这非常有用。
- 我可以在任何时候对我的工作队列进行汇聚,因此为了知道它们是否被汇聚,我用单词“漏斗”标记
funnelQ
。
- 在程序执行中,我使用
dispatch_sync
将一些任务分派到workerQ
队列,由于某种原因,我想要将任务分派到funnelQ
队列,但避免分派到当前队列,所以我检查标记并根据情况采取行动。由于获取值是向上遍历层次结构,因此该值不会在workerQ
中找到,但会在funnelQ
中找到。这是一种找出任何队列是否为我们存储值的队列的方法。因此,可以防止分派到当前队列。
如果您想知道读写上下文数据的函数,有三个:
dispatch_queue_set_specific
: 写入队列。
dispatch_queue_get_specific
: 从队列中读取。
dispatch_get_specific
: 用于从当前队列中读取的方便函数。
键通过指针进行比较,永远不会被解除引用。设置器中的最后一个参数是释放键的析构函数。
如果您想知道“指向另一个队列”的含义,它的确就是这个意思。例如,我可以将队列A指向主队列,这将导致队列A中的所有块在主队列上运行(通常用于UI更新)。
dispatch_get_current_queue()
现已被弃用。检测主队列的方法是使用NSThread.isMainThread()
(Swift)或[NSThread isMainThread](Objective-C)。 - udondanNSThread.isMainThread()
不可靠,因为在极少数情况下,主队列会阻塞,并且 GCD 会重用主线程来执行其他队列。请参见 1,2。 - Jano