[ios] 다른 블록을 시작하기 전에 두 개의 비동기 블록이 실행될 때까지 대기

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, DispatchGroupnotify(qos:flags:queue:execute:)DispatchQueueasync(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, DispatchGroupwait(), DispatchGroupenter()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:).


#삼. 사용 및 의Dispatch​Work​Item​Flags barrierDispatchQueueasync(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, Dispatch​Work​Item​FlagsbarrierDispatchQueueasync(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, DispatchSemaphorewait()DispatchSemaphoresignal()

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. 사용 OperationQueueOperationaddDependency(_:)

Apple Developer API Reference는 다음에 대해 설명합니다 Operation​Queue.

작업 대기열은 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. 사용 OperationQueueOperationQueueaddBarrierBlock(_:)(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();
}