http://www.itdecent.cn/p/888ea823c8a5
http://www.itdecent.cn/p/1e9f3a5f4932
hello,各位讀者,我又回來了啦,感覺上一篇的文章各位的反映還算不錯(cuò),感謝各位讓我有堅(jiān)持寫作的動(dòng)力。好了,前話就說這么多了,開始我們今天要說的主題了,最近博主在開發(fā)中碰到一個(gè)問題,開啟兩個(gè)主要異步線程,兩個(gè)異步線程內(nèi)部又得分別開啟一個(gè)異步線程和其他耗時(shí)操作,最后還有第三個(gè)線程,這第三個(gè)線程必須等到前兩個(gè)主要線程內(nèi)部所有操作都完成以后再去執(zhí)行,但是在執(zhí)行以上這些操作的時(shí)候不能卡住界面,以下是我簡單畫的一個(gè)需求分析圖
可能有些讀者一眼看下去,會(huì)覺得這不是一個(gè)簡單的線程組能搞掂的事情嘛?也許是的,那么我們來嘗試一下,對(duì)需求進(jìn)行線程組的功能制作
1.第一個(gè)主要線程
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) {
// 請(qǐng)求完成,可以通知界面刷新界面等操作
NSLog(@"第一步網(wǎng)絡(luò)請(qǐng)求完成");
}];
[task resume];
// 以下還要進(jìn)行一些其他的耗時(shí)操作
NSLog(@"耗時(shí)操作繼續(xù)進(jìn)行");
});
2.第二個(gè)主要線程(跟第一線程的操作是一樣的。但是請(qǐng)求的地址不一樣)
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) {
// 請(qǐng)求完成,可以通知界面刷新界面等操作
NSLog(@"第二步網(wǎng)絡(luò)請(qǐng)求完成");
}];
[task resume];
// 以下還要進(jìn)行一些其他的耗時(shí)操作
NSLog(@"耗時(shí)操作繼續(xù)進(jìn)行");
});
3.第三個(gè)主要線程(等待前兩個(gè)線程完全完成后再進(jìn)行)
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新界面等在主線程的操作");
});
那么,上面代碼一眼看下去就很明了了,開啟一個(gè)線程組,然后兩個(gè)異步線程組 dispatch_group_t,最后一個(gè) dispatch_group_notify 來執(zhí)行依賴操作,但是當(dāng)博主開開心心的跑去 command + R 的時(shí)候,奇怪的事情發(fā)生了,以下是是輸出的結(jié)果:
1 `NSLog(@"第一步網(wǎng)絡(luò)請(qǐng)求完成");`
2 `NSLog(@"刷新界面等在主線程的操作");`
3 `NSLog(@"第二步網(wǎng)絡(luò)請(qǐng)求完成");`
讀者不難發(fā)現(xiàn),我們的數(shù)序不對(duì)了,第三步操作(刷新界面操作)明明是要第一步和第二步操作完成后才能進(jìn)行的,我們已經(jīng)設(shè)置了 dispatch_group_notify 了,但是為什么不是按照我們的思路去走呢?
其實(shí)這個(gè)道理很簡單,我們開啟的網(wǎng)絡(luò)請(qǐng)求,是一個(gè)異步線程,所謂的異步線程,就是告訴系統(tǒng)你不要管我是否完成了,你盡管執(zhí)行其他操作,開一個(gè)線程讓我到外面操作去執(zhí)行就行了,所以我們傻傻的 dispatch_group_async 自然就不會(huì)管網(wǎng)絡(luò)操作是否完成,是否有數(shù)據(jù)了,直接執(zhí)行下面操作,告訴 dispatch_group_notify 它已經(jīng)完成就行了。
但這怎么辦好呢?所以我們要引入了我們今天要用到的 多線程的信號(hào)量 dispatch_semaphore_t 了,那么 dispatch_semaphore_t又怎么理解么?
` dispatch_semaphore_t` :通俗的說我們可以理解成他是一個(gè)紅綠燈的信號(hào),當(dāng)它的信號(hào)量為0時(shí)(紅燈)等待,
當(dāng)信號(hào)量為1或大于1時(shí)(綠燈)走。
以下就是它的創(chuàng)建跟使用:
// 創(chuàng)建一個(gè)信號(hào),value:信號(hào)量
dispatch_semaphore_create(<#long value#>)
// 使某個(gè)信號(hào)的信號(hào)量+1
dispatch_semaphore_signal(<#dispatch_semaphore_t dsema#>)
// 某個(gè)信號(hào)進(jìn)行等待, timeout:等待時(shí)間,永遠(yuǎn)等待為 DISPATCH_TIME_FOREVER
dispatch_semaphore_wait(<#dispatch_semaphore_t dsema#>, <#dispatch_time_t timeout#>)
那么我們的代碼可以改寫為
// 設(shè)置一個(gè)異步線程組
dispatch_group_async(group, dispatch_queue_create("com.dispatch.test", DISPATCH_QUEUE_CONCURRENT), ^{
// 設(shè)置一個(gè)網(wǎng)絡(luò)請(qǐng)求
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.github.com"]];
// 創(chuàng)建一個(gè)信號(hào)量為0的信號(hào)(紅燈)
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(@"第一步操作");
// 使信號(hào)的信號(hào)量+1,這里的信號(hào)量本來為0,+1信號(hào)量為1(綠燈)
dispatch_semaphore_signal(sema);
}];
[task resume];
// 以下還要進(jìn)行一些其他的耗時(shí)操作
NSLog(@"耗時(shí)操作繼續(xù)進(jìn)行");
// 開啟信號(hào)等待,設(shè)置等待時(shí)間為永久,直到信號(hào)的信號(hào)量大于等于1(綠燈)
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
});
根據(jù)上面的寫法,當(dāng)線程執(zhí)行到 dispatch_semaphore_wait 的時(shí)候如果網(wǎng)絡(luò)請(qǐng)求還沒有完成,那么信號(hào)就會(huì)繼續(xù)等待,這個(gè)異步線程組就不會(huì)執(zhí)行完畢,這樣就能達(dá)到我們的需求了。
當(dāng)然 `dispatch_semaphore_signal` 的用途可不止這么一個(gè)喔,有興趣的讀者可以去多看看喔。
@end
關(guān)于信號(hào)量,一般可以用停車來比喻。
停車場剩余4個(gè)車位,那么即使同時(shí)來了四輛車也能停的下。如果此時(shí)來了五輛車,那么就有一輛需要等待。
信號(hào)量的值就相當(dāng)于剩余車位的數(shù)目,dispatch_semaphore_wait函數(shù)就相當(dāng)于來了一輛車,dispatch_semaphore_signal
就相當(dāng)于走了一輛車。停車位的剩余數(shù)目在初始化的時(shí)候就已經(jīng)指明了(dispatch_semaphore_create(long value)),
調(diào)用一次dispatch_semaphore_signal,剩余的車位就增加一個(gè);調(diào)用一次dispatch_semaphore_wait剩余車位就減少一個(gè);
當(dāng)剩余車位為0時(shí),再來車(即調(diào)用dispatch_semaphore_wait)就只能等待。有可能同時(shí)有幾輛車等待一個(gè)停車位。有些車主
沒有耐心,給自己設(shè)定了一段等待時(shí)間,這段時(shí)間內(nèi)等不到停車位就走了,如果等到了就開進(jìn)去停車。而有些車主就像把車停在這,
所以就一直等下去。