GCD(三) dispatch_group 调度群组

dispatch_group

Posted by HuberyYang on June 27, 2017

假设一下下面的场景:某APP首页分为多个功能模块,每个模块使用不同的数据接口,为了提升用户体验,在加载首页时可以先将所有模块数据拿到后再加载UI,但不用考虑哪个模块先得到数据。这种情况下使用GCD的 dispatch_group 调度群组 很容易处理。

dispatch_group 包含的函数

/* 生成 group 实例 */
dispatch_group_create()

/* group  调度群组
 * queue  任务执行队列
 * block  任务内容 */
dispatch_group_async(<#dispatch_group_t _Nonnull group#>, <#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)

/* group  调度群组,与被提交的函数相关联。该群组被系统保留,直到应用程序中定义好的函数执行完毕。不可以为NULL。
 * queue  调度队列,用于提交系统定义好的函数,异步调用。该队列被系统保留,直到应用程序定义好的函数执行完毕。不可以为NULL。
 * context  是由程序定义的,用于传递给work的参数。
 * work  应用程序定义的函数,在目标队列中调用。work的第一个参数值是context。*/
dispatch_group_async_f(<#dispatch_group_t _Nonnull group#>, <#dispatch_queue_t _Nonnull queue#>, <#void * _Nullable context#>, <#dispatch_function_t _Nonnull work#>) 

/* 调用一次该函数会使当前群组中未执行任务的计数加一,与 `dispatch_group_leave` 一起使用,两者调用次数应保持一致。
 * group  调度群组 */
dispatch_group_enter(<#dispatch_group_t _Nonnull group#>)

/* 调用一次该函数会使当前群组中未执行任务的计数减一,与`dispatch_group_enter` 一起使用,两者调用次数应保持一致。
 * group  调度群组 */
dispatch_group_leave(<#dispatch_group_t _Nonnull group#>)

/* 等待调度群组关联的block内任务执行完成,等待期间会堵塞当前线程,任务完成后恢复正常,执行后续任务。
 * group  调度群组
 * timeout  等待时间 */
dispatch_group_wait(<#dispatch_group_t _Nonnull group#>, <#dispatch_time_t timeout#>)

/* 作用与 `dispatch_group_wait` 类似,但不会堵塞当前线程,并且可以指定group内任务完成之后后续任务的执行队列,后续任务在block中执行
 * group —— 调度群组
 * queue —— 后续任务执行队列
 * block —— 在这里添加后续任务 */
dispatch_group_notify(<#dispatch_group_t _Nonnull group#>, <#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)

使用dispatch_group_wait

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();

for (int index = 0; index < 5; index ++) {

    dispatch_group_async(group, queue, ^{

        NSLog(@"s1 线程:%@ -- index = %d",[NSThread currentThread],index);
        [NSThread sleepForTimeInterval:3.0f];
        NSLog(@"s2 线程:%@ -- index = %d",[NSThread currentThread],index);
    });

}

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);    
NSLog(@"执行后续任务");

使用dispatch_group_notify

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();

for (int index = 0; index < 5; index ++) {

    dispatch_group_async(group, queue, ^{

       NSLog(@"s1 线程:%@ -- index = %d",[NSThread currentThread],index);
       [NSThread sleepForTimeInterval:3.0f];
       NSLog(@"s2 线程:%@ -- index = %d",[NSThread currentThread],index);

    });

}

dispatch_group_notify(group, queue, ^{

    NSLog(@"执行后续任务");
});

dispatch_group_enter 与 dispatch_group_leave 的使用

如果在调度群组关联的block内直接异步提交新的任务,group 不会等待嵌套的异步任务执行完毕后在进入 dispatch_group_notifydispatch_group_wait 状态,如:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
dispatch_group_t group = dispatch_group_create();

for (int index = 0; index < 5; index ++) {

    dispatch_group_async(group, queue, ^{

       NSLog(@"s1 线程:%@ -- index = %d",[NSThread currentThread],index);
       [NSThread sleepForTimeInterval:3.0f];
       NSLog(@"s2 线程:%@ -- index = %d",[NSThread currentThread],index);

       dispatch_async(queue, ^{
          [NSThread sleepForTimeInterval:2.0f];
          NSLog(@"s3 线程:%@ -- index = %d",[NSThread currentThread],index);
       });

    });    
}

dispatch_group_notify(group, queue, ^{

   NSLog(@"后续任务");
});

从结果可以看出,后续任务没有在s1、s2、s3都执行完成后执行,此时就可以使用dispatch_group_enter 告诉调度群有组新的任务加入,使未完成任务数增加;使用dispatch_group_leave 使未完成任务数减少

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();

for (int index = 0; index < 5; index ++) {

    dispatch_group_async(group, queue, ^{

        NSLog(@"s1 线程:%@ -- index = %d",[NSThread currentThread],index);
        [NSThread sleepForTimeInterval:3.0f];
        NSLog(@"s2 线程:%@ -- index = %d",[NSThread currentThread],index);

        //有新任务加入
        dispatch_group_enter(group);

        dispatch_async(queue, ^{
            [NSThread sleepForTimeInterval:2.0f];
            NSLog(@"s3 线程:%@ -- index = %d",[NSThread currentThread],index);

            //有任务完成
            dispatch_group_leave(group);
        });

    });

}

dispatch_group_notify(group, queue, ^{

    NSLog(@"后续任务");
});

此外,dispatch_group_enter 与 dispatch_group_leave 也可以直接使用,但需要保持两者调用次数一致,配合异步执行,其效果和 dispatch_group_async 类似

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();

for (int index = 0; index < 5; index ++) {

    dispatch_group_enter(group);

    dispatch_async(queue, ^{

        NSLog(@"s1 线程:%@ -- index = %d",[NSThread currentThread],index);
        [NSThread sleepForTimeInterval:3.0f];
        NSLog(@"s2 线程:%@ -- index = %d",[NSThread currentThread],index);

        dispatch_group_leave(group);

    });
}

dispatch_group_notify(group, queue, ^{

    NSLog(@"后续任务");
});