Ожидание выполнения двух асинхронных блоков перед запуском другого блока

при использовании 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
});

8 ответов


использовать группы отправки: см. здесь например, "ожидание групп задач в очереди" в главе "очереди отправки" руководства по программированию параллелизма библиотеки разработчиков iOS от Apple

ваш пример может выглядеть так:

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

расширение на Jörn Eyrich ответ (upvote его ответ, если вы upvote этот), если у вас нет контроля над dispatch_async вызовы для ваших блоков, как и в случае асинхронных блоков завершения, вы можете использовать группы GCD с помощью dispatch_group_enter и 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!

Другой альтернативой 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");  
});

просто создайте параллельную очередь, отправьте два блока, а затем отправьте последний блок с барьером, который заставит его ждать завершения двух других.


С Swift 3, Центральная Диспетчерская предлагает много способов решить вашу проблему. В соответствии с вашими потребностями, вы можете выбрать один из шесть моделей показано в следующих фрагментах Playground.


#1. Используя DispatchGroup, DispatchGroup notify(qos:flags:queue:execute:) и DispatchQueue async(group:qos:flags:execute:) методы

Руководство По Программированию Параллелизма Разработчика Apple государств о 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:) или mix DispatchGroup enter() и DispatchGroup leave() С DispatchGroup notify(qos:flags:queue:execute:).


#3. Используя Dispatch​Work​Item​Flags barrier собственность и DispatchQueue DispatchQueue async(group:qos:flags:execute:) метод

учебник Grand Central Dispatch для Swift 3: Часть 1/2 статья из Raywenderlich.com дает определение для препятствия:

барьеры отправки-это группа функций, действующих как узкое место последовательного стиля при работе с параллельными очередями. [...] При отправке 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​Flags 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() методы

Соруш Khanlou написал следующие строки руководство GCD блог:

используя семафор, мы можем блокировать поток в течение произвольного количества времени, пока не будет отправлен сигнал от другого потока. Семафоры, как и остальная часть GCD, потокобезопасны, и их можно запускать из любого места. Семафоры можно использовать, когда есть асинхронный API, который вам нужно сделать синхронным, но вы не можете изменить его.

Apple Developer API Ссылка также дает следующее обсуждение для DispatchSemaphore init(value:​) инициализатор:

передача нуля для значения полезна, когда двум потокам необходимо согласовать завершение определенного события. Передача значения больше нуля полезна для управления конечным пулом ресурсов, где размер пула равен значение.

использование:

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 и BlockOperation

ссылка Apple Developer API заявляет о 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
 */

Я знаю, что вы спрашивали о 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];

первый ответ по существу правильный, но если вы хотите самый простой способ достичь желаемого результата, вот автономный пример кода, демонстрирующий, как это сделать с семафором (который также является тем, как группы отправки работают за кулисами, 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();
}

ответы выше все круто, но все они пропустили одну вещь. группа выполняет задачи (блоки) в потоке, куда она вошла при использовании 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 чтобы получить то, что вы хотеть.


Не сказать, что другие ответы не велики для определенных обстоятельств, но это один фрагмент, который я всегда использую из Google:

- (void)runSigninThenInvokeSelector:(SEL)signInDoneSel {


    if (signInDoneSel) {
        [self performSelector:signInDoneSel];
    }

}