异步顺序执行任务

有些时候,我们会想要一个同步的网络请求,而不用在AFN的回调里等待,层层嵌套。此外一些异步操作也希望按顺序来执行,

场景

之前手头上的项目有几个上传资料的页面,大概就是一个列表,用户一项项地选择资料(如图片)上传。

一开始我的做法是让用户全部选完,点击提交时,通过一些资料校验条件后开始上传,后来发现这样的体验有点问题,因为有些页面有十来个选项,平均每个选项两三张图片,而且由于后台限制,同时上传的线程不能超过2个,网络一般的情况下要等待10秒以上。

安卓那边的做法是,用户选完一项后立刻上传,为了不报错,会卡主线程,直到上传完毕,这样用户选完后至少会有一两秒的卡顿才能进行下一项操作。显然这样的处理方法也不尽如人意。

解决问题

只要做到按顺序执行异步操作即可,封装在工具类里,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 信号量控制并发数
static dispatch_semaphore_t semaphore;
// 并行队列
static dispatch_queue_t queue;
// 调度组
static dispatch_group_t group;
// 调度组通知在等待调用
static bool isWait;

+ (dispatch_semaphore_t)semaphore {
[self initSyncConfig];
return semaphore;
}

+ (dispatch_queue_t)queue {
[self initSyncConfig];
return queue;
}

+ (dispatch_group_t)group {
[self initSyncConfig];
return group;
}
/// 初始化
+ (void)initSyncConfig {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
semaphore = dispatch_semaphore_create(1);
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
group = dispatch_group_create();
});
}

#pragma mark - 异步顺序执行任务
/*!
*
* @abstract
* 异步顺序执行任务
*
* @discussion
* 任务执行完,将信号量+1即可 eg: dispatch_semaphore_signal(semaphore)
*
* @param operation
* 需要异步顺序执行的任务
*/
+ (void)syncTask:(void (^)(dispatch_semaphore_t))operation {
// block 为空
if (!operation) return;
dispatch_group_async([self group], [self queue], ^{
dispatch_semaphore_wait([self semaphore], DISPATCH_TIME_FOREVER);
operation([self semaphore]);
dispatch_semaphore_wait([self semaphore], DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(semaphore);
});
// isWait 确保只有一个调度组通知
if (!isWait) {
isWait = YES;
dispatch_group_notify(group, queue, ^{
isWait = NO;
XDLog(@"调度组任务完成");
});
}
}

对外暴露 + (void)syncTask:(void (^)(dispatch_semaphore_t))operation 这个类方法,且主线程回调里必须执行 dispatch_semaphore_signal(semaphore);

原理

主要利用了信号量和调度组,把传过来的operation放到异步去执行,在异步里首先执行 dispatch_semaphore_wait([self semaphore], DISPATCH_TIME_FOREVER); 这样会使信号量减1,信号量本来是1,现在是0,往下走回再减1,信号量为-1,这时会卡住这个异步线程,后面的operation只进入调度组的队列,不能执行。

当异步的主线程回调里执行 dispatch_semaphore_signal(semaphore);使信号量加1,让线程继续往下执行,往下还是 dispatch_semaphore_signal(semaphore); 执行完这个任务后信号量重新置为了1,下一个任务开始执行。

这样写可能还不够优雅,如果能hook住每一个任务的主线程回调,在里面执行dispatch_semaphore_signal(semaphore);,而不是在每次调用这个方法在回调里执行,这样的封装会好很多。