Ожидание выполнения двух асинхронных блоков перед запуском другого блока
при использовании 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. Используя DispatchWorkItemFlags
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
, 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()
методы
Соруш 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 заявляет о 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
*/
Я знаю, что вы спрашивали о 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];
}
}