《编写高质量iOS与OS X代码的52个有效方法》--第六章 第44条
(ps:此乃读书笔记,加深记忆,仅供大家参考)
第44条:通过Dispatch Group机制,根据系统资源状况来执行任务
dispatch group是GCD的一项特性,能够把任务分组。调用者可以等待这组任务执行完毕,也可以在提供回调函数之后继续往下执行,这组任务完成时,调用者会得到通知。这个功能有许多用途,其中最重要、最值得注意的用法,就是把将要并发执行的多个任务合为一组,于是调用者就可以知道这些任务何时才能全部执行完毕。
下面这个函数可以创建dispatch group:
dispatch_group_t group = dispatch_group_create();
dispatch group就是个简单的数据结构,这种结构彼此之间没什么区别,它不像派发队列,后者还有个用来区别身份的标识符。想把任务编组,有两种办法。第一种是下面这个函数:
void dispatch_group_async(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
它是普通的dispatch_async函数的变体,比原来多一个函数,用于表示待执行的块所归属的组。还有种办法能够指定任务所属的dispatch group,那就是下面这一对函数:
dispatch_group_enter(dispatch_group_t group)
dispatch_group_leave(dispatch_group_t group)
前者能够使分组里正要执行的任务数递增,而后者则使之递减。由此可知,调用了dispatch_group_enter以后,必须又与之对应的dispatch_group_leave才行。这与引用计数(参见第29条)相似,要使用引用计数。就必须令保留操作与释放操作彼此对应,以防内存泄漏。
下面这个函数可用于等待dispatch group执行完毕:
void dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)
此函数接受两个参数,一个是要等待的group,另一个是代表等待时间的timeout值。timeout参数表示函数在等待dispatch group执行完毕时,应该阻塞多久。如果执行dispatch group所需的时间小于timeout,则返回0,否则返回非0值。此参数也可以取常量DISPATCH_TIME_FOREVER,这表示函数会一直等待dispatch group执行完,而不会超时(time out)。
除了可以用上面那个函数等待dispatch group执行完毕之外,也可以换个办法,使用下列函数:
void dispatch_group_notify(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
与wait函数略有不同的是:开发者可以向此函数传入块,等dispatch group执行完毕之后,块会在特定的线程上执行。加入当前此案成不应阻塞,而开发者又想在那些任务全部完成时得到通知,那么此做法就很有必要了。
如果想令数组中的每个对象都执行某项任务,并且想等待所有任务执行完毕,那么就可以使用这个GCD特性来实现。代码如下:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t dispatchGroup = dispatch_group_create();
NSArray * collection;
for (id object in collection) {
dispatch_group_async(dispatchGroup, queue, ^{
[object description];
});
}
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER);
//Continue processing after completing tasks
若当前线程不应阻塞,则可用notify函数来取代wait:
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{
//Continue processing after completing tasks
});
notify回调时所选用的队列,完全应该根据具体情况来定。笔者在范例代码中使用了主线程队列,这种是常见写法。也可以用自定义的串行队列或者全局并发队列。
在本例中,所有任务都派发到同一队列之中。但实际上未必一定要这样做。也可以把某些任务放在优先级高的线程上执行,同时仍然把所有任务都归入同一个dispatch group,并在执行完毕时获得通知:
dispatch_queue_t lowPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t highPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_group_t dispatchGroup = dispatch_group_create();
NSArray * lowPriorityObject;
NSArray * highPriorityObject;
for (id object in lowPriorityObject) {
dispatch_group_async(dispatchGroup, lowPriorityQueue, ^{
[object description];
});
}
for (id object in highPriorityObject) {
dispatch_group_async(dispatchGroup, highPriorityQueue, ^{
[object description];
});
}
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{
//Continue processing after completing tasks
});
除了像上面这样把任务提交到并发队列之外,也可以把任务提交至各个串行队列中,并用dispatch group跟踪其执行状况。然而,如果所有任务都排在同一个串行队列里面,那么dispatch group就用处不大了。因为此时,任务总要逐个执行,所以只需在提交完全部任务之后再提交一个块即可,这样做与通过notify函数等待dispatch group执行完毕后再回调块是等效的:
dispatch_queue_t queue = dispatch_queue_create("com.effectiveobjectivec.queue", NULL);
NSArray *collection;
for (id object in collection) {
dispatch_async(queue, ^{
[object description];
});
}
dispatch_async(queue, ^{
//Continue processing after completing tasks
});
笔者为何要在标题中谈到“根据系统资源状况来执行任务”呢?回头看看向并发队列派发任务的那个例子,就会明白了。为了执行队列中的块,GCD会在适当的时机自动创建新线程或或复用旧线程。如果并发队列,那么其中有可能会有多个线程,这也就意味着多个块可以并发执行。在并发队列中,执行任务所用的并发线程数量,取决于各个因素,而GCD主要是根据系统资源状况来判定这些因素的。加入CPU有多个核心,并且队列中有大量任务等待执行,那那么GCD就可能会给该队列配备多个线程。通过dispatch group所提供的这种简便方式,既可以并发执行一系列给定的任务,又能在全部任务结束时得到通知。由于GCD有并发队列机制,所以能够根据可用的系统资源状况来并发执行任务。
在前面的范例代码中,我们遍历某个collection,并在其每个元素上执行任务,而这也可以用另外一个GCD函数来实现:
void dispatch_apply(size_t iterations, dispatch_queue_t queue,void (^block)(size_t));
此函数将块反复执行一定的次数,每次传给块的参数值都会递增,从0开始,直至“iterations-1”。其用法如下:
dispatch_queue_t queue = dispatch_queue_create("com.effectiveobjectivec.queue", NULL);
dispatch_apply(10, queue, ^(size_t i) {
//Perform task
});
有一件事要注意:dispatch_apply所用的队列可以是并发队列。如果采用并发队列,那么系统就可以根据资源状况来并行执行这些块了,这与使用dispatch group的那段范例代码一样。
这再次表明,未必总要使用dispatch group。然而,dispatch_apply会持续阻塞,直到所有任务都执行完毕为止。由此可见,假如把块派给了当前队列(或者体系中高于当前队列的某个串行队列),就将导致死锁。若想在后台执行任务,则应使用dispatch group。
要点
- 一系列任务可归入一个dispatch group之中。开发者可以在这组任务执行完毕时获得通知。
- 通过dispatch group,可以在并发式派发队列里同时执行多项任务。此时,GCD会根据系统资源状况来调度这些并发执行的任务。开发者若自己来实现此功能,则需编写大量代码。
网友评论