最近博主在开发中碰到一个问题,开启两个主要异步线程,两个异步线程http://www.maiziedu.com/course/23/内部又得分别开启一个异步线程和其他耗时操作,最后还有第三个线程,这第三个线程必须等到前两个主要线程内部所有操作都完成以后再去执行,但是在执行以上这些操作的时候不能卡住界面,以下是我简单画的一个需求分析图
需求分析图
可能有些读者一眼看下去,会觉得这不是一个简单的线程组能搞掂的事情嘛?也许是的,那么我们来尝试一下,对需求进行线程组的功能制作
1.第一个主要线程
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_queue_create("com.dispatch.test", DISPATCH_QUEUE_CONCURRENT), ^{
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.baidu.com"]];
NSURLSessionDownloadTask *task = [[NSURLSession sharedSession] downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 请求完成,可以通知界面刷新界面等操作
NSLog(@"第一步网络请求完成");
}];
[task resume];
// 以下还要进行一些其他的耗时操作
NSLog(@"耗时操作继续进行");
});
2.第二个主要线程(跟第一线程的操作是一样的。但是请求的地址不一样)
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_queue_create("com.dispatch.test", DISPATCH_QUEUE_CONCURRENT), ^{
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.github.com"]];
NSURLSessionDownloadTask *task = [[NSURLSession sharedSession] downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 请求完成,可以通知界面刷新界面等操作
NSLog(@"第二步网络请求完成");
}];
[task resume];
// 以下还要进行一些其他的耗时操作
NSLog(@"耗时操作继续进行");
});
3.第三个主要线程(等待前两个线程完全完成后再进行)
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新界面等在主线程的操作");
});
那么,上面代码一眼看下去就很明了了,开启一个线程组,然后两个异步线程组 dispatch_group_t,最后一个dispatch_group_notify 来执行依赖操作,但是当博主开开心心的跑去 command + R 的时候,奇怪的事情发生了,以下是是输出的结果:
1 `NSLog(@"第一步网络请求完成");
`2 `NSLog(@"刷新界面等在主线程的操作");
`3 `NSLog(@"第二步网络请求完成");`
读者不难发现,我们的数序不对了,第三步操作(刷新界面操作)明明是要第一步和第二步操作完成后才能进行的,我们已经设置了 dispatch_group_notify 了,但是为什么不是按照我们的思路去走呢?
其实这个道理很简单,我们开启的网络请求,是一个异步线程,所谓的异步线程,就是告诉系统你不要管我是否完成了,你尽管执行其他操作,开一个线程让我到外面操作去执行就行了,所以我们傻傻的 dispatch_group_async 自然就不会管网络操作是否完成,是否有数据了,直接执行下面操作,告诉 dispatch_group_notify 它已经完成就行了。
但这怎么办好呢?所以我们要引入了我们今天要用到的 多线程的信号量 dispatch_semaphore_t 了,那么 dispatch_semaphore_t又怎么理解么?
` dispatch_semaphore_t` :通俗的说我们可以理解成他是一个红绿灯的信号,当它的信号量为0时(红灯)等待,
当信号量为1或大于1时(绿灯)走。
以下就是它的创建跟使用:
// 创建一个信号,value:信号量
dispatch_semaphore_create(<#long value#>)// 使某个信号的信号量+1
dispatch_semaphore_signal(<#dispatch_semaphore_t dsema#>)// 某个信号进行等待, timeout:等待时间,永远等待为DISPATCH_TIME_FOREVER
dispatch_semaphore_wait(<#dispatch_semaphore_t dsema#>, <#dispatch_time_t timeout#>)
那么我们的代码可以改写为
// 设置一个异步线程组
dispatch_group_async(group, dispatch_queue_create("com.dispatch.test", DISPATCH_QUEUE_CONCURRENT), ^{
// 设置一个网络请求
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.github.com"]];
// 创建一个信号量为0的信号(红灯)
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSURLSessionDownloadTask *task = [[NSURLSession sharedSession] downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"第一步操作");
// 使信号的信号量+1,这里的信号量本来为0,+1信号量为1(绿灯)
dispatch_semaphore_signal(sema);
}];
[task resume];
// 以下还要进行一些其他的耗时操作
NSLog(@"耗时操作继续进行");
// 开启信号等待,设置等待时间为永久,直到信号的信号量大于等于1(绿灯)
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
});
根据上面的写法,当线程执行到 dispatch_semaphore_wait 的时候如果网络请求还没有完成,那么信号就会继续等待,这个异步线程组就不会执行完毕,这样就能达到我们的需求了。
当然 `dispatch_semaphore_signal` 的用途可不止这么一个喔,有兴趣的读者可以去多看看喔。
@end
原文链接:简书/止于浮水