iOS-GCD的学习记录(2)

来源:互联网 发布:java ee se me 区别 编辑:程序博客网 时间:2024/05/20 11:31
1.Dispatch Group

Dispatch Group用于创建一组队列。有时你可能需要等dispatch队列中的所有任务完成了才执行一个任务。当所有任务都在一个串行队列里面的时候,你只需要将最后一个任务加到队列最后就可以了。但如果你在使用并行队列的时候或者面对多个队列的时候,看起来就没那么简单了。这种情况下,你就可以使用Dispatch Group。下面的代码演示了Dispatch Group的基本用法,代码中,3个block被添加到了全局dispatch队列上,当所有block都完成后,最终一个block会在主线程队列上执行。


dispatch_queue_t serialQueue = dispatch_queue_create("SerialQueue", NULL);        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);        dispatch_group_t group = dispatch_group_create();    dispatch_group_async(group, queue, ^{NSLog(@"blk0");});    dispatch_group_async(group, queue, ^{NSLog(@"blk1");});    dispatch_group_async(group, queue, ^{NSLog(@"blk2");});//    dispatch_group_notify(group,dispatch_get_main_queue(), ^{NSLog(@"done");});        dispatch_group_notify(group,serialQueue, ^{sleep(3); NSLog(@"done");});    dispatch_group_notify(group,serialQueue, ^{sleep(3); NSLog(@"done");});

任务的执行顺序不会每次都相同,因为它们是被加到全局的并发队列上的,它们会被并发的在多个线程上执行。然而”done”总是会在最后被执行。

不管是在何种队列上,dispatch group都会监控任务的完成情况。当它发现所有任务都完成后,最终一个任务就会被加到队列上。这就是使用dispatch group的方法。

首先,用dispatch_group_create函数创建一个dispatch_group_t类型的dispatch group。因为函数名字中包含的”create”,当你不再需要dispatch group后,要将它释放掉。像对dispatch队列那样,用dispatch_release函数将它释放掉。

接着,像用dispatch_async函数那样,用dispatch_group_async函数将block加到指定的队列上。与dispatch_async函数的不同之处在于,这个函数需要一个dispatch group来作为第一个参数。当调用dispatch_group_async函数的时候,block就和group关联起来了。和将block加到dispatch队列上的情况类似,当一个block和dispatch group关联起来后,block也就会通过dispatch_retain函数获得dispatch group的所有权。当block执行完毕后,也会通过dispatch_release函数释放掉对group的所有权。你不需要去关心block和group是如何关联起来的。

最后,dispatch_group_notify函数将一个block加到了一个dispatch队列上。这个block会在group中的所有任务完成后被执行。函数的第一个参数是一个要通知的dispatch group。当和这个group关联的所有任务完成后,第三个参数中的block会被添加到指定的队列(第二个参数)上。不管传递给dispatch_group_notify函数的是何种队列,当block被添加到队列上时,group相关联的所有任务一定已经全部完成了。


另外,如下面代码所示的这样,你还可以使用dispatch_group_wait函数,等待dispatch group的所有任务完成。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);    dispatch_group_t group = dispatch_group_create();    dispatch_group_async(group, queue, ^{sleep(3); NSLog(@"blk0");});    dispatch_group_async(group, queue, ^{NSLog(@"blk1");});    dispatch_group_async(group, queue, ^{NSLog(@"blk2");});        dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);//    long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);    long result = dispatch_group_wait(group, time);    if (result == 0)    {        /*         * dispatch group相关联的所有任务都已经完成         */        NSLog(@"11");    }    else    {        /*         * dispatch group相关联的一些任务仍然在执行         */         NSLog(@"222");    }

dispatch_group_wait函数的第二个参数是等待的时间,dispatch_time_t类型。这个例子中,DISPATCH_TIME_FOREVER表示永远等待。它会一直等待,直到dispatch group相关联的所有任务都完成。不能在中途取消等待。

dispatch_group_wait函数没有返回0,则说明当指定的等待时间过后,dispatch group相关联的一些任务仍然还在执行。如果它返回0,则说明所有任务都已经完成。如果指定的等待时间是DISPATCH_TIME_FOREVER,那么dispatch_group_wait函数肯定会返回0,因为必须要等待所有任务完成后才能返回。


等等,那这里的”wait”是什么意思?是指当dispatch_group_wait函数被调用后,函数不会立即返回,当前执行dispatch_group_wait函数的这个线程会停止,当超过指定的等待时间后或者dispatch group相关联的所有任务都完成后,这个线程才会继续,函数才会返回结果。


当等待时间被指定为DISPATCH_TIME_NOW的时候,这个函数可以被用来检查dispatch group相关联的所有任务是否都已经完成。

long result = dispatch_group_wait(group, DISPATCH_TIME_NOW);

例如,你可以在主线程的RunLoop上毫无延迟的检查每次loop中是否所有任务都完成了,然后再做某些操作。尽管可以这样干,但是我建议还是用dispatch_group_notify函数好些,这样你的代码可以更简洁。


2.dispatch_barrier_async

dispatch_barrier_async函数用于等待队列中其他任务完成。前面我们提到过,当你访问数据库或者一个文件的时候,你可以用串行队列来避免数据冲突。但实际上,当有其他更新数据或读取数据的操作在进行的时候,不应该在同时在进行更新数据的操作。但是多个读取操作是可以同时进行的,这样可以更高效的访问数据,这种情况下,更新数据的操作必须放到一个串行队列中,并且只有当串行队列中没有任何更新操作在执行的时候,这些读取操作才能被放到一个并行队列中。你要确保不能在更新操作完成前开始读取操作。你可以用dispatch group和dispatch_set_target_queue函数来实现这种功能,但是会很复杂。GCD提供了一个更好的解决方案,这就是dispatch_barrier_async函数了。下面的代码创建了一个并发队列,然后向队列中添加了一些读取操作。


dispatch_queue_t queue = dispatch_queue_create("SerialQueue", DISPATCH_QUEUE_CONCURRENT);    dispatch_async(queue, ^{        NSLog(@"read1");    });        dispatch_async(queue, ^{        NSLog(@"read2");    });    dispatch_async(queue, ^{        NSLog(@"read3");    });    dispatch_barrier_async(queue, ^{        NSLog(@"write");    });        dispatch_async(queue, ^{        NSLog(@"read4");    });        dispatch_async(queue, ^{        NSLog(@"read5");    });        dispatch_async(queue, ^{        NSLog(@"read6");    });

这种地方就要用dispatch_barrier_async函数了。用dispatch_barrier_async函数,你可以将一个任务在所有任务都完成后才加入到并发队列中。当用dispatch_barrier_async函数添加的任务完成后,并发队列中的任务又可以恢复并发执行了。图7-9演示了下面这段代码的执行情况。

如你所见,就是这么简单,只需要用dispatch_barrier_async代替dispatch_async就可以了。
所以,为了更高效的访问数据库或文件,尽情的使用并发队列和dispatch_barrier_async函数吧。


3.dispatch_sync

dispatch_sync函数和dispatch_async函数类似都是用于将一个任务添加到一个队列上,但是dispatch_sync会等待添加到队列上的任务执行完毕。dispatch_async函数名字中的”async”代表异步(asynchronous)。因此,它将一个block添加到一个队列上后,block会异步的执行,dispatch_async不会等待任务执行完毕。如图7-10所示。


与此对应的同步版本(synchronous)——dispatch_sync函数,它将block添加到队列上后,会等到block执行完毕后才返回。如图7-11所示。


像前面dispatch_group_wait函数那一小节中解释过的那样,等待表示当前线程会暂停。例如,你可能想在主线程上使用一个在其他线程上的全局队列上运行的任务的结果,这种情况,你就可以用dispatch_sync函数。

dispatch_queue_t queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dispatch_sync(queue, ^{/* a task */});

当dispatch_sync函数被调用后,函数不会立即返回,它会等待任务执行完成后才返回。这有点像是一个简单版本的dispatch_group_wait函数。如你所见,dispatch_sync函数用起来非常简单,虽然它可能会引起死锁问题。例如,下面的代码如果在主线程上运行,就会造成死锁。


dispatch_queue_t queue = dispatch_get_main_queue();dispatch_sync(queue, ^{NSLog(@"Hello?");});

代码中,一个block被加到了主线程队列上,但同时,它又会等待block完成后才能继续。因为这段代码是运行在主线程上面的,被加到主线程队列上的这个block永远也无法被执行。
再看下面这段代码:

dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_async(queue, ^{dispatch_sync(queue, ^{NSLog(@"Hello?");});});

dispatch_async添加block后,虽然会立即返回,但是因为dispatch_async添加的这个block是加到主线程队列上的,而后面dispatch_sync这个函数添加的block也是加到主线程队列上的,也会造成死锁。
同样,这个问题也会发生在一个串行队列上。

dispatch_queue_t queue =dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);dispatch_async(queue, ^{dispatch_sync(queue, ^{NSLog(@"Hello?");});});

另外,类似的,dispatch_barrier_async函数中包含了一个”async”,同样这个函数也有一个”sync”版本,叫做dispatch_barrier_sync。当队列中的所有任务执行完成后,由dispatch_barrier_sync函数添加的这个任务会开始执行,并且函数会等待任务完成后才返回,像dispatch_sync函数一样。
当使用这些同步的(synchronous)API的时候,比如dispatch_sync,它们会等待任务完成后才继续。当你使用这些API的时候要想想为什么要使用这些API,要注意不要造成死锁。


4.dispatch_apply

dispatch_apply函数和dispatch_sync函数以及dispatch group有点关系。它用来将一个block多次添加到dispatch队列上,然后等待所有任务完成。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);        dispatch_apply(10, queue, ^(size_t index) {        NSLog(@"%zu", index);        NSLog(@"线程===%d",[NSThread isMainThread]);    });    NSLog(@"done");

结果


可以看到其实线程并不是只是子线程,也可以主线程,原因是当前可用并发队列,系统自动分配的。。。不是一个线程哦,是个队列


因为是执行在全局队列上面的,所以每个任务的执行时间不一定相同。但是,“done”总会在最后执行,因为dispatch_apply函数会等待所有任务完成。

函数中,第一个参数是添加的次数,第二个参数是目标队列,第三个参数就是要添加到目标队列上的block了。在上面这个例子中,可以看到block带有一个参数,这个参数是用来区分是第几次block的,因为block会被多次添加到队列上。比如,如果你想要对一个NSArray中的所有对象进行某些操作的时候,就不需要用for循环的方式了。如下代码所示。


dispatch_queue_t queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dispatch_apply([array count], queue, ^(size_t index) {NSLog(@"%zu: %@", index, [array objectAtIndex:index]);});

如你所见,在一个全局队列上对数组中的所有对象执行相同的处理非常容易。dispatch_apply函数会像dispatch_sync函数一样等待所有任务完成,所以建议你将dispatch_apply和dispatch_async函数一起结合使用。如下代码所示。


dispatch_queue_t queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);/* * 在全局dispatch上并发执行 */dispatch_async(queue, ^{/*  * 在全局dispatch队列上,dispatch_apply会等待所有任务完成。  */dispatch_apply([array count], queue, ^(size_t index) {/*  * 并发地对数组中的所有对象进行一些处理  */NSLog(@"%zu: %@", index, [array objectAtIndex:index]);});/* * dispatch_apply添加的所有任务都完成了。 *//* * 在主线程dispatch队列上异步执行某些任务。 */dispatch_async(dispatch_get_main_queue(), ^{/* * 这里是要在主线程队列上执行的任务。 * 比如更新用户界面等等。 */NSLog(@"done");});});

参考文章:GCD使用详解

0 0
原创粉丝点击