通过Dispatch Group机制,根据系统资源状况来执行任务

dispatch group是GCD的一项特性,能够把任务分组,调用者可以等待这组任务执行完毕,也可以在提供回调函数之后继续往下执行,这组任务完成时,调用者会得到通知。

这个功能有多个用途,其中最重要,最值得注意的用法,就是把将要并发执行的多个任务合为一个组,于是调用者就可以知道这些任务合适才能全部执行完毕。

举个栗子:把一系列压缩文件的任务表示成dispatch group。

下面这个函数可以创建dispatch group:

  dispatch_group_tdispatch_group_create();

dispatch group就是哥简单的数据结构,这种结构彼此之间没什么区别,它不像派发队列,后者还有个用来区别身份的标示符,想把任务编组,有两种办法,第一种是用下面这个函数:

void dispatch_group_async(dispatch_group_t group,

              dispatch_queue_t queue,

              dispatch_block_t block);

它是普通dispatch_async函数的变体,比原来多了一个参数,用于表示待执行的块所归属的组。还有中办法能够制定任务所属的dispatch group,那就使用下面这对函数:

void dispatch_group_enter(dispatch_group_t group)

void dispatch_group_leave(dispatch_group_t group)

前者能够使分组里正要执行的任务数递增,而后者则使之递减。由此可知,调用了dispatch_group_enter以后,必须有与之对应的dispatch_group_leave才行。这与引用计数相似,要使用引用计数,就必须令保留操作与释放操作彼此对应以防内存泄露。而在使用dispatch group时,如果调用enter之后,没有相应的leave操作,那么这一组任务就永远执行不完。

下面这个函数可以用于等待dispatch group执行完毕:

  long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);

此函数接受两个参数,一个是要等待的group,另一个是代表等待的时间timeout值。该参数表示函数在等待dispatch group执行完毕是,应该阻塞多久。如果执行dispatch group所需的时间小于timeout,则返回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();
for (id object in collection){
    dispatch_group_async(dispatchGroup,queue,^{
        [object performTask];
    );      
}                            
diapatch_group_wait(dispatchGroup,DISPATCH_TIME_FOREVER);

若当前线程不应阻塞,则可用notify函数来取代wait:

dispatch_queue_t notifyQueue = dispatch_get_main_queue();

dispatch_group_notify(dispatchGroup,notifyQueue,^{
  //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();

for(id object in lowPriorityObjects){

  dispatch_group_async(dispatchGroup,lowPriorityQueue,^{

    [object performTask];

  }

} 

for(id object in highPriorityObjects){

  dispatch_group_async(dispatchGroup,highPriorityQueue,^{

    [object performTask];

  }

} 

dispatch_queue_t notifyQueue = dispatch_get_main_queue();

dispatch_group_notify(dispatchGroup,notifyQueue,^{

  //continue processing after completing tasks  

});

除了像上面这样把任务提交到并发队列之外,也可以把任务提交至各个串行队列中,并用dispatch group跟踪其执行状况,然而,如果所有任务都排在同一个串行队列里面,那么dispatchgroup用处就不大了。因为此时任务总要逐个执行,所以只需在提交完全部任务之后再提交一个块即可,这样做与通用notify函数等待dispatch group执行完毕然后再回调块是等效的:

dispatch_queue_t queue = dispatch_queue_create("com.effectiveobjectivc.queue",NULL);

for(id object in collection){

  dispatch_async(queue,^{

    [object perfomTask];
  });

}

dispatch_async(queue,^{

//continue processing after completing tasks
  }

);

上面这段代码表明,开发者未必总需要使用dispatch group,有时候采用单个队列搭配标准的异步派发,也可实现同样效果。

笔者为何要在标题中谈到“根据系统资源状况来执行任务”呢?回头看看向并发队列派发任务的那个例子,就明白了。为了执行队列中的块,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

  }

)

采用简单的for 循环,从0递增至9,也能实现同样的效果:

for(int i = 0;i<10;i++){//perform task}

有一件事需要注意:dispatch_apply所用的队列可以是并发队列,如果采用并发队列,那么系统可以根据资源状况来并行执行这些块了,这与使用dispatch group的那段范例代码一样。上面这个for循环要处理的collection若是数组,则可用dispatch_apply改写如下:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

dispatch_queue_apply(array.coun,queue,^(size_t i){

    id object = array[i];

    [object performTask];  

  };

);

这个例子再次表明:未必总要使用dispatch_group,然而,dispatch_apply会持续阻塞,直到所有任务都执行完毕为止。由此可以见加入把块派给当前队列(或者体系中高于当前队列的某个串行队列),就讲导致死锁,若想在后台执行任务,则应使用dispatch group。

要点:

  • 一系列任务可归入一个dispatch group中,开发者可以在这组任务执行完毕时获得通知
  • 通过dispatch group,可以在并发式派发队列里同时执行多项任务。此时GCD会根据系统资源状况来调度这些并发执行的任务。开发者若是自己来实现此功能,则需编写大量代码

摘自 《编写高质量iOS与OS X代码的52个有效方法》 第四十四条。

原文地址:https://www.cnblogs.com/lingzhiguiji/p/4009346.html