GCD를 사용할 때 다음 실행 단계로 넘어 가기 전에 두 개의 비동기 블록이 실행될 때까지 기다립니다. 가장 좋은 방법은 무엇입니까?
우리는 다음을 시도했지만 작동하지 않는 것 같습니다.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
// block1
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
// block2
});
// wait until both the block1 and block2 are done before start block3
// how to do that?
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
// block3
});
답변
디스패치 그룹 사용 : Apple iOS 개발자 라이브러리 동시성 프로그래밍 안내서의 “큐 대기”장의 “대기중인 작업 그룹 대기”에 대한 예제는 여기 를 참조 하십시오 .
귀하의 예는 다음과 같습니다.
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
// block1
NSLog(@"Block1");
[NSThread sleepForTimeInterval:5.0];
NSLog(@"Block1 End");
});
dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
// block2
NSLog(@"Block2");
[NSThread sleepForTimeInterval:8.0];
NSLog(@"Block2 End");
});
dispatch_group_notify(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
// block3
NSLog(@"Block3");
});
// only for non-ARC projects, handled automatically in ARC-enabled projects.
dispatch_release(group);
다음과 같이 출력을 생성 할 수 있습니다.
2012-08-11 16:10:18.049 Dispatch[11858:1e03] Block1
2012-08-11 16:10:18.052 Dispatch[11858:1d03] Block2
2012-08-11 16:10:23.051 Dispatch[11858:1e03] Block1 End
2012-08-11 16:10:26.053 Dispatch[11858:1d03] Block2 End
2012-08-11 16:10:26.054 Dispatch[11858:1d03] Block3
답변
dispatch_async
비동기 완료 블록의 경우와 같이 블록 호출을 제어 할 수없는 경우, Jörn Eyrich 응답에서 확장 (자신의 응답을 찬성하면 그의 답을 찬성) 하십시오. 완료 완료 블록의 경우 dispatch_group_enter
와 마찬가지로 GCD 그룹을 사용 하여 dispatch_group_leave
직접 사용할 수 있습니다.
이 예에서, 우리는 척할 computeInBackground
수없는 것입니다 (대리자 콜백, NSURLConnection completionHandler 또는 기타) 상상할 수 있으므로 디스패치 호출에 액세스 할 수 없습니다.
// create a group
dispatch_group_t group = dispatch_group_create();
// pair a dispatch_group_enter for each dispatch_group_leave
dispatch_group_enter(group); // pair 1 enter
[self computeInBackground:1 completion:^{
NSLog(@"1 done");
dispatch_group_leave(group); // pair 1 leave
}];
// again... (and again...)
dispatch_group_enter(group); // pair 2 enter
[self computeInBackground:2 completion:^{
NSLog(@"2 done");
dispatch_group_leave(group); // pair 2 leave
}];
// Next, setup the code to execute after all the paired enter/leave calls.
//
// Option 1: Get a notification on a block that will be scheduled on the specified queue:
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"finally!");
});
// Option 2: Block an wait for the calls to complete in code already running
// (as cbartel points out, be careful with running this on the main/UI queue!):
//
// dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // blocks current thread
// NSLog(@"finally!");
이 예에서 computeInBackground : completion :은 다음과 같이 구현됩니다.
- (void)computeInBackground:(int)no completion:(void (^)(void))block {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"%d starting", no);
sleep(no*2);
block();
});
}
출력 (런에서 타임 스탬프 포함) :
12:57:02.574 2 starting
12:57:02.574 1 starting
12:57:04.590 1 done
12:57:06.590 2 done
12:57:06.591 finally!
답변
Swift 5.1에서 Grand Central Dispatch 는 문제를 해결하는 여러 가지 방법을 제공합니다. 필요 에 따라 다음 놀이터 스 니펫에 표시된 7 가지 패턴 중 하나를 선택할 수 있습니다 .
#1. 사용 DispatchGroup
, DispatchGroup
의 notify(qos:flags:queue:execute:)
와 DispatchQueue
의async(group:qos:flags:execute:)
Apple Developer Concurrency Programming Guide는 다음에 대해 설명합니다DispatchGroup
.
디스패치 그룹은 하나 이상의 작업이 완료 될 때까지 스레드를 차단하는 방법입니다. 지정된 모든 작업이 완료 될 때까지 진행할 수없는 장소에서이 동작을 사용할 수 있습니다. 예를 들어, 일부 데이터를 계산하기 위해 여러 태스크를 디스패치 한 후 그룹을 사용하여 해당 태스크를 기다린 후 완료되면 결과를 처리 할 수 있습니다.
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)
let group = DispatchGroup()
queue.async(group: group) {
print("#1 started")
Thread.sleep(forTimeInterval: 5)
print("#1 finished")
}
queue.async(group: group) {
print("#2 started")
Thread.sleep(forTimeInterval: 2)
print("#2 finished")
}
group.notify(queue: queue) {
print("#3 finished")
}
/*
prints:
#1 started
#2 started
#2 finished
#1 finished
#3 finished
*/
# 2. 사용 DispatchGroup
, DispatchGroup
의 wait()
, DispatchGroup
의 enter()
와 DispatchGroup
의를leave()
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)
let group = DispatchGroup()
group.enter()
queue.async {
print("#1 started")
Thread.sleep(forTimeInterval: 5)
print("#1 finished")
group.leave()
}
group.enter()
queue.async {
print("#2 started")
Thread.sleep(forTimeInterval: 2)
print("#2 finished")
group.leave()
}
queue.async {
group.wait()
print("#3 finished")
}
/*
prints:
#1 started
#2 started
#2 finished
#1 finished
#3 finished
*/
당신은 또한 혼합 할 수 있습니다 DispatchGroup
wait()
으로 DispatchQueue
async(group:qos:flags:execute:)
또는 혼합 DispatchGroup
enter()
하고 DispatchGroup
leave()
와 DispatchGroup
notify(qos:flags:queue:execute:)
.
#삼. 사용 및 의DispatchWorkItemFlags
barrier
DispatchQueue
async(group:qos:flags:execute:)
Swift 4 : Grand Central Dispatch Tutorial : Raywenderlich.com의 1/2 부 기사는 장벽에 대한 정의를 제공합니다 .
디스패치 배리어는 동시 큐 작업시 직렬 스타일 병목 현상으로 작동하는 기능 그룹입니다.
DispatchWorkItem
디스패치 큐에 제출할 때 해당 특정 시간 동안 지정된 큐에서 실행되는 유일한 항목임을 나타내도록 플래그를 설정할 수 있습니다. 이는 발송 장벽 이전에 큐에 제출 된 모든 항목DispatchWorkItem
이 실행 전에 완료되어야 함을 의미합니다 .
용법:
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)
queue.async {
print("#1 started")
Thread.sleep(forTimeInterval: 5)
print("#1 finished")
}
queue.async {
print("#2 started")
Thread.sleep(forTimeInterval: 2)
print("#2 finished")
}
queue.async(flags: .barrier) {
print("#3 finished")
}
/*
prints:
#1 started
#2 started
#2 finished
#1 finished
#3 finished
*/
# 4. 사용 DispatchWorkItem
, DispatchWorkItemFlags
의 barrier
와 DispatchQueue
의async(execute:)
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)
queue.async {
print("#1 started")
Thread.sleep(forTimeInterval: 5)
print("#1 finished")
}
queue.async {
print("#2 started")
Thread.sleep(forTimeInterval: 2)
print("#2 finished")
}
let dispatchWorkItem = DispatchWorkItem(qos: .default, flags: .barrier) {
print("#3 finished")
}
queue.async(execute: dispatchWorkItem)
/*
prints:
#1 started
#2 started
#2 finished
#1 finished
#3 finished
*/
# 5. 사용 DispatchSemaphore
, DispatchSemaphore
의 wait()
와 DispatchSemaphore
의signal()
Soroush Khanlou는 GCD 핸드북 블로그 게시물 에 다음과 같은 내용을 썼습니다 .
세마포를 사용하면 다른 스레드의 신호가 전송 될 때까지 임의의 시간 동안 스레드를 차단할 수 있습니다. 나머지 GCD와 마찬가지로 세마포어는 스레드로부터 안전하며 어디에서나 트리거 할 수 있습니다. 세마포어는 동기식으로 만들어야하는 비동기식 API가있는 경우 사용할 수 있지만 수정할 수는 없습니다.
Apple Developer API Reference는 DispatchSemaphore
init(value:)
이니셜 라이저에 대한 다음 설명도 제공합니다 .
값에 0을 전달하면 두 스레드가 특정 이벤트의 완료를 조정해야 할 때 유용합니다. 0보다 큰 값을 전달하면 한정된 리소스 풀을 관리하는 데 유용합니다. 여기서 풀 크기는 값과 같습니다.
용법:
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)
let semaphore = DispatchSemaphore(value: 0)
queue.async {
print("#1 started")
Thread.sleep(forTimeInterval: 5)
print("#1 finished")
semaphore.signal()
}
queue.async {
print("#2 started")
Thread.sleep(forTimeInterval: 2)
print("#2 finished")
semaphore.signal()
}
queue.async {
semaphore.wait()
semaphore.wait()
print("#3 finished")
}
/*
prints:
#1 started
#2 started
#2 finished
#1 finished
#3 finished
*/
# 6. 사용 OperationQueue
및 Operation
의addDependency(_:)
Apple Developer API Reference는 다음에 대해 설명합니다 OperationQueue
.
작업 대기열은
libdispatch
라이브러리 (Grand Central Dispatch라고도 함)를 사용하여 작업 실행을 시작합니다.
용법:
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let operationQueue = OperationQueue()
let blockOne = BlockOperation {
print("#1 started")
Thread.sleep(forTimeInterval: 5)
print("#1 finished")
}
let blockTwo = BlockOperation {
print("#2 started")
Thread.sleep(forTimeInterval: 2)
print("#2 finished")
}
let blockThree = BlockOperation {
print("#3 finished")
}
blockThree.addDependency(blockOne)
blockThree.addDependency(blockTwo)
operationQueue.addOperations([blockThree, blockTwo, blockOne], waitUntilFinished: false)
/*
prints:
#1 started
#2 started
#2 finished
#1 finished
#3 finished
or
#2 started
#1 started
#2 finished
#1 finished
#3 finished
*/
# 7. 사용 OperationQueue
및 OperationQueue
의 addBarrierBlock(_:)
(iOS 13 필요)
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let operationQueue = OperationQueue()
let blockOne = BlockOperation {
print("#1 started")
Thread.sleep(forTimeInterval: 5)
print("#1 finished")
}
let blockTwo = BlockOperation {
print("#2 started")
Thread.sleep(forTimeInterval: 2)
print("#2 finished")
}
operationQueue.addOperations([blockTwo, blockOne], waitUntilFinished: false)
operationQueue.addBarrierBlock {
print("#3 finished")
}
/*
prints:
#1 started
#2 started
#2 finished
#1 finished
#3 finished
or
#2 started
#1 started
#2 finished
#1 finished
#3 finished
*/
답변
또 다른 GCD 대안은 장벽입니다.
dispatch_queue_t queue = dispatch_queue_create("com.company.app.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"start one!\n");
sleep(4);
NSLog(@"end one!\n");
});
dispatch_async(queue, ^{
NSLog(@"start two!\n");
sleep(2);
NSLog(@"end two!\n");
});
dispatch_barrier_async(queue, ^{
NSLog(@"Hi, I'm the final block!\n");
});
동시 대기열을 생성하고 두 블록을 디스패치 한 다음 장벽과 함께 마지막 블록을 디스패치하면 다른 두 블록이 끝날 때까지 기다릴 수 있습니다.
답변
나는 당신이 GCD에 관해 물었다는 것을 알고 있지만, 원한다면, NSOperationQueue
이런 종류의 것들도 정말로 우아하게 처리합니다.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"Starting 3");
}];
NSOperation *operation;
operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"Starting 1");
sleep(7);
NSLog(@"Finishing 1");
}];
[completionOperation addDependency:operation];
[queue addOperation:operation];
operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"Starting 2");
sleep(5);
NSLog(@"Finishing 2");
}];
[completionOperation addDependency:operation];
[queue addOperation:operation];
[queue addOperation:completionOperation];
답변
위의 답변은 모두 멋지지만 모두 한 가지를 놓쳤습니다. 그룹은 dispatch_group_enter
/ 를 사용할 때 입력 한 스레드에서 작업 (블록)을 실행합니다 dispatch_group_leave
.
- (IBAction)buttonAction:(id)sender {
dispatch_queue_t demoQueue = dispatch_queue_create("com.demo.group", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(demoQueue, ^{
dispatch_group_t demoGroup = dispatch_group_create();
for(int i = 0; i < 10; i++) {
dispatch_group_enter(demoGroup);
[self testMethod:i
block:^{
dispatch_group_leave(demoGroup);
}];
}
dispatch_group_notify(demoGroup, dispatch_get_main_queue(), ^{
NSLog(@"All group tasks are done!");
});
});
}
- (void)testMethod:(NSInteger)index block:(void(^)(void))completeBlock {
NSLog(@"Group task started...%ld", index);
NSLog(@"Current thread is %@ thread", [NSThread isMainThread] ? @"main" : @"not main");
[NSThread sleepForTimeInterval:1.f];
if(completeBlock) {
completeBlock();
}
}
이것은 작성된 동시 큐에서 실행됩니다 demoQueue
. 대기열을 만들지 않으면 기본 스레드 에서 실행됩니다 .
- (IBAction)buttonAction:(id)sender {
dispatch_group_t demoGroup = dispatch_group_create();
for(int i = 0; i < 10; i++) {
dispatch_group_enter(demoGroup);
[self testMethod:i
block:^{
dispatch_group_leave(demoGroup);
}];
}
dispatch_group_notify(demoGroup, dispatch_get_main_queue(), ^{
NSLog(@"All group tasks are done!");
});
}
- (void)testMethod:(NSInteger)index block:(void(^)(void))completeBlock {
NSLog(@"Group task started...%ld", index);
NSLog(@"Current thread is %@ thread", [NSThread isMainThread] ? @"main" : @"not main");
[NSThread sleepForTimeInterval:1.f];
if(completeBlock) {
completeBlock();
}
}
다른 스레드에서 작업을 실행하는 세 번째 방법이 있습니다.
- (IBAction)buttonAction:(id)sender {
dispatch_queue_t demoQueue = dispatch_queue_create("com.demo.group", DISPATCH_QUEUE_CONCURRENT);
// dispatch_async(demoQueue, ^{
__weak ViewController* weakSelf = self;
dispatch_group_t demoGroup = dispatch_group_create();
for(int i = 0; i < 10; i++) {
dispatch_group_enter(demoGroup);
dispatch_async(demoQueue, ^{
[weakSelf testMethod:i
block:^{
dispatch_group_leave(demoGroup);
}];
});
}
dispatch_group_notify(demoGroup, dispatch_get_main_queue(), ^{
NSLog(@"All group tasks are done!");
});
// });
}
물론 언급했듯이 dispatch_group_async
원하는 것을 얻을 수 있습니다 .
답변
첫 번째 대답은 본질적으로 정확하지만 원하는 결과를 얻는 가장 간단한 방법을 원한다면 세마포어로 작업을 수행하는 방법을 보여주는 독립 실행 형 코드 예제가 있습니다 (이것은 디스패치 그룹이 장면 뒤에서 작동하는 방식 인 JFYI입니다) :
#include <dispatch/dispatch.h>
#include <stdio.h>
main()
{
dispatch_queue_t myQ = dispatch_queue_create("my.conQ", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t mySem = dispatch_semaphore_create(0);
dispatch_async(myQ, ^{ printf("Hi I'm block one!\n"); sleep(2); dispatch_semaphore_signal(mySem);});
dispatch_async(myQ, ^{ printf("Hi I'm block two!\n"); sleep(4); dispatch_semaphore_signal(mySem);});
dispatch_async(myQ, ^{ dispatch_semaphore_wait(mySem, DISPATCH_TIME_FOREVER); printf("Hi, I'm the final block!\n"); });
dispatch_main();
}