在写 OneShare 的时候,发送推特和微博都是使用的第三方库,它们都是使用的异步发送。这时遇到了需要获得这两个异步操作的返回结果,再进行下一步操作的场景。来用个具体的代码例子来说明下,假设现在有三个任务 A、B、C,其中 C 必须在 A、B 之后执行,A、B 无要求。
像下面这样直接用 GCD 异步执行的话,可以看出无法保证 C 在 A、B 执行完成之后再执行,C 任务会在 A 和 B 还未完成就执行,
dispatch_queue_t queue = dispatch_queue_create("me.slowwalker.defaultQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"A START");
[NSThread sleepForTimeInterval:3];
NSLog(@"A FINISH");
});
dispatch_async(queue, ^{
NSLog(@"B START");
[NSThread sleepForTimeInterval:1];
NSLog(@"B FINISH");
});
dispatch_async(queue, ^{
NSLog(@"C -> AFTER A&B");
});
// 输出结果:
// 07:46:25.048 WaitAsyncDemo[85891:5296876] A START
// 07:46:25.048 WaitAsyncDemo[85891:5296859] B START
// 07:46:25.048 WaitAsyncDemo[85891:5296861] C -> AFTER A&B
// 07:46:26.116 WaitAsyncDemo[85891:5296859] B FINISH
// 07:46:28.113 WaitAsyncDemo[85891:5296876] A FINISH
查看官方的 Concurrency Programming Guide 文档会发现 GCD 提供了[三种队列相关操作](https://developer.apple.com/library/content/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW30),分别是 Dispatch groups,Dispatch semaphores 和 Dispatch sources。仔细研究下文档,会发现前两者都能用于解决前文提到的问题。
Dispatch Groups
苹果对 dispatch groups 的描述就是: a way to monitor a set of block objects for completion,还给出了一个使用例子 Waiting on Groups of Queued Tasks,可以看出这个 API 的应用方法就是对应此文的场景。具体代码如下:
dispatch_queue_t queue = dispatch_queue_create("me.slowwalker.defaultQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
// dispatch_group_async 方式
dispatch_group_async(group, queue, ^{
NSLog(@"A START");
[NSThread sleepForTimeInterval:3];
NSLog(@"A FINISH");
});
dispatch_group_async(group, queue, ^{
NSLog(@"B Start");
[NSThread sleepForTimeInterval:1];
NSLog(@"B Finish");
});
// 通知 main——queue 可以执行 C 任务了
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"C - AFTER A&B(Into main queue)");
});
除了官方文档中 dispatch_group_async 这种调用方式,还有另外一种方式,用 dispatch_group_enter 表示添加任务,用 dispatch_group_leave 表示执行完成。这里要注意的是,一定要成双成对。
// dispatch_group_enter/dispatch_group_leave 配对方式
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"A START");
[NSThread sleepForTimeInterval:3];
NSLog(@"A FINISH");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"B Start");
[NSThread sleepForTimeInterval:1];
NSLog(@"B Finish");
dispatch_group_leave(group);
});
Dispatch Semaphores
Dispatch semaphores call down to the kernel only when the calling thread needs to be blocked because the semaphore is unavailable.
这是官方的定义,大意就是可以通过 semaphore 来调度线程。官方同时还给了个使用介绍:
- When you create the semaphore (using the dispatch_semaphore_create function), you can specify a positive integer indicating the number of resources available.
- In each task, call dispatch_semaphore_wait to wait on the semaphore.
- When the wait call returns, acquire the resource and do your work.
- When you are done with the resource, release it and signal the semaphore by calling the dispatch_semaphore_signalfunction.
大致就是在创建完 Semaphore 后,调用 dispatch_semaphore_wait 询问系统是否能执行内部代码,它会接收一个 Semaphore 信号和时间值,若信号的信号量为 0,则会阻塞当前线程,直到信号量大于 0 或者经过输入的时间值;若信号量大于 0,则会使信号量减1并返回,程序继续住下执行。运行完调用 dispatch_semaphore_signal 告诉 Semaphore 释放资源,信号量加 1。如果用来解决此文中的存在于同一个队列中的问题,显得有点大材小用,更适合解决两个不同的队列之间调度问题。
dispatch_queue_t queueA = dispatch_queue_create("me.slowwalker.semaphoreQueueA", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queueB = dispatch_queue_create("me.slowwalker.semaphoreQueueB", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(queueA, ^{
NSLog(@"QueueA START");
[NSThread sleepForTimeInterval:3];
NSLog(@"QueueA FINISH");
dispatch_semaphore_signal(semaphore);
});
NSLog(@"semaphore");
dispatch_async(queueB, ^{
NSLog(@"QueueB START");
[NSThread sleepForTimeInterval:1];
NSLog(@"QueueB FINISH");
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"C -> AFTER A&B");
// 运行结果:
// 2017-06-18 23:51:29.698 WaitAsyncDemo[4610:389664] QueueA START
// 2017-06-18 23:51:29.698 WaitAsyncDemo[4610:389946] QueueB START
// 2017-06-18 23:51:30.702 WaitAsyncDemo[4610:389946] QueueB FINISH
// 2017-06-18 23:51:32.702 WaitAsyncDemo[4610:389664] QueueA FINISH
// 2017-06-18 23:51:32.703 WaitAsyncDemo[4610:389626] C -> AFTER A&B
除了这个场景,还有一个典型的场景,就是多线程下,同时对一个文件或者可变数组的操作,也就是线程安全的问题。等仔细研究后,再来总结下。
本文代码地址:Github