Ric*_*III 58 multithreading objective-c grand-central-dispatch objective-c-blocks
我遇到了一个场景,我有一个委托回调,可能发生在主线程或另一个线程上,我不知道哪个直到运行时(使用StoreKit.framework).
我还需要在该回调中更新UI代码,这需要在函数执行之前发生,因此我最初的想法是拥有这样的函数:
-(void) someDelegateCallback:(id) sender
{
dispatch_sync(dispatch_get_main_queue(), ^{
// ui update code here
});
// code here that depends upon the UI getting updated
}
Run Code Online (Sandbox Code Playgroud)
当它在后台线程上执行时,它工作得很好.但是,当在主线程上执行时,程序陷入僵局.
这本身似乎对我有意思,如果我读的文档dispatch_sync正确的,那么我希望它只是执行彻底,不担心它安排到runloop块,如说在这里:
作为优化,此函数在可能的情况下调用当前线程上的块.
但是,这不是太大的交易,它只是意味着更多的打字,这导致我采用这种方法:
-(void) someDelegateCallBack:(id) sender
{
dispatch_block_t onMain = ^{
// update UI code here
};
if (dispatch_get_current_queue() == dispatch_get_main_queue())
onMain();
else
dispatch_sync(dispatch_get_main_queue(), onMain);
}
Run Code Online (Sandbox Code Playgroud)
然而,这似乎有点倒退.这是制作GCD的一个错误,还是我在文档中遗漏了什么?
Jan*_*ano 72
dispatch_sync 做两件事:
鉴于主线程是一个串行队列(这意味着它只使用一个线程),以下语句:
dispatch_sync(dispatch_get_main_queue(), ^(){/*...*/});
Run Code Online (Sandbox Code Playgroud)
将导致以下事件:
dispatch_sync 将块排队到主队列中.dispatch_sync 阻止主队列的线程,直到块完成执行.dispatch_sync 因为块应该运行的线程被阻塞而永远等待. 理解这一点的关键是dispatch_sync不执行块,它只对它们进行排队.执行将在运行循环的未来迭代中发生.
以下方法:
if (queueA == dispatch_get_current_queue()){
block();
} else {
dispatch_sync(queueA,block);
}
Run Code Online (Sandbox Code Playgroud)
完全没问题,但请注意,它不会保护您免受涉及队列层次结构的复杂场景的影响.在这种情况下,当前队列可能与您尝试发送块的先前阻塞的队列不同.例:
dispatch_sync(queueA, ^{
dispatch_sync(queueB, ^{
// dispatch_get_current_queue() is B, but A is blocked,
// so a dispatch_sync(A,b) will deadlock.
dispatch_sync(queueA, ^{
// some task
});
});
});
Run Code Online (Sandbox Code Playgroud)
对于复杂的情况,在调度队列中读/写键值数据:
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;
// saves string "funnel" in funnelQ
CFStringRef tag = CFSTR("funnel");
dispatch_queue_set_specific(funnelQ,
&kKey,
(void*)tag,
(dispatch_function_t)CFRelease);
dispatch_sync(workerQ, ^{
// is funnelQ in the hierarchy of workerQ?
CFStringRef tag = dispatch_get_specific(&kKey);
if (tag){
dispatch_sync(funnelQ, ^{
// some task
});
} else {
// some task
}
});
Run Code Online (Sandbox Code Playgroud)
说明:
workerQ指向funnelQ队列的队列.在实际代码中,如果您有多个"工作"队列并且想要一次性恢复/暂停(通过恢复/更新其目标funnelQ队列来实现),这将非常有用.funnelQ用"漏斗"这个词标记.dispatch_sync的东西workerQ,而不管是什么原因,我想dispatch_sync给funnelQ的,但避免dispatch_sync当前的队列,所以我检查了标签,并采取相应的行动.因为get在层次结构中向上移动,所以不会找到该值,workerQ但会在其中找到funnelQ.这是一种查明层次结构中是否存在值的队列的方法.因此,要防止dispatch_sync到当前队列.如果您想知道读/写上下文数据的函数,有三个:
dispatch_queue_set_specific:写入队列.dispatch_queue_get_specific:从队列中读取.dispatch_get_specific:从当前队列中读取的便捷功能.密钥通过指针进行比较,从不解除引用.setter中的最后一个参数是释放密钥的析构函数.
如果您想知道"将一个队列指向另一个队列",那就意味着这一点.例如,我可以将队列A指向主队列,它将导致队列A中的所有块在主队列中运行(通常这是为UI更新完成的).
law*_*cko 51
不要从传递给函数调用的同一队列上执行的任务调用dispatch_sync函数.这样做会使队列死锁.如果需要调度到当前队列,请使用dispatch_async函数异步执行此操作.
另外,我按照你提供的链接和dispatch_sync的描述我读了这个:
调用此函数并以当前队列为目标会导致死锁.
所以我不认为这是GCD的问题,我认为唯一合理的方法是你在发现问题后发明的方法.
Mec*_*cki 15
我知道你的困惑来自哪里:
作为优化,此函数在可能的情况下调用当前线程上的块.
小心,它说当前的线程.
线程!=队列
队列不拥有线程,线程未绑定到队列.有线程,有队列.每当队列想要运行一个块时,它就需要一个线程,但这并不总是相同的线程.它只需要任何线程(这可能每次都是不同的),当它完成运行块(暂时)时,同一个线程现在可以由不同的队列使用.
这句话所讨论的优化是关于线程,而不是关于队列.例如,考虑你有两个串行队列,QueueA并且QueueB现在你做到以下几点:
dispatch_async(QueueA, ^{
someFunctionA(...);
dispatch_sync(QueueB, ^{
someFunctionB(...);
});
});
Run Code Online (Sandbox Code Playgroud)
当QueueA运行块时,它将暂时拥有一个线程,任何线程.someFunctionA(...)将在该线程上执行.现在在进行同步调度时,QueueA不能做任何事情,它必须等待调度完成.QueueB另一方面,还需要一个线程来运行它的块并执行someFunctionB(...).因此要么QueueA暂时挂起它的线程并QueueB使用其他一些线程来运行块或将QueueA其线程交给QueueB(毕竟它不需要它,直到同步调度完成)并QueueB直接使用当前线程QueueA.
不用说最后一个选项要快得多,因为不需要线程切换.而这是句子谈到了优化.因此dispatch_sync(),对于不同的队列,可能并不总是导致线程切换(不同的队列,可能是相同的线程).
但是dispatch_sync()仍然不能发生在同一个队列中(相同的线程,是的,相同的队列,没有).这是因为队列将在块之后执行,当它执行一个块时,它将不执行另一个队列,直到当前执行完毕.因此,执行BlockA和BlockA做了dispatch_sync()的BlockB在同一个队列.BlockB只要它仍然运行BlockA,队列就不会运行,但是在运行BlockA之前运行不会继续BlockB.看到问题?这是一个经典的僵局.
文档明确指出传递当前队列将导致死锁.
现在他们没有说明他们为什么设计这样的东西(除了它实际上需要额外的代码使其工作),但我怀疑这样做的原因是因为在这种特殊情况下,块会"跳跃"队列,即在正常情况下,你的块在队列上的所有其他块运行之后最终运行,但在这种情况下它将在之前运行.
当您尝试将GCD用作互斥机制时会出现此问题,并且此特定情况等同于使用递归互斥锁.我不想讨论是否更好地使用GCD或传统的互斥API(如pthreads互斥体),或者使用递归互斥体是否是个好主意; 我会让其他人争论,但肯定有这个需求,特别是当它是你正在处理的主要队列时.
就个人而言,我认为dispatch_sync如果支持这个或者有另一个提供备用行为的函数会更有用.我会敦促其他人这么认为向Apple提交错误报告(正如我所做的那样,ID:12668073).
您可以编写自己的函数来执行相同的操作,但这有点像黑客:
// Like dispatch_sync but works on current queue
static inline void dispatch_synchronized (dispatch_queue_t queue,
dispatch_block_t block)
{
dispatch_queue_set_specific (queue, queue, (void *)1, NULL);
if (dispatch_get_specific (queue))
block ();
else
dispatch_sync (queue, block);
}
Run Code Online (Sandbox Code Playgroud)
NB以前,我有一个使用dispatch_get_current_queue()的示例,但现在已弃用.
| 归档时间: |
|
| 查看次数: |
28505 次 |
| 最近记录: |