等待两个异步块执行后再开始另一个块

在使用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
});
133553 次浏览

使用调度组:例如,在苹果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

第一个答案本质上是正确的,但如果你想用最简单的方法来实现预期的结果,这里有一个独立的代码示例演示如何使用信号量(这也是调度组在幕后工作的方式,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();
}

我知道你问了关于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];

另一个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");
});

只需创建一个并发队列,分派你的两个块,然后用barrier分派最后一个块,这将使它等待其他两个块完成。

扩展Jörn Eyrich的回答(如果你对这个问题投了赞成票,请为他的回答投票),如果你无法控制你的块的dispatch_async调用,就像异步完成块的情况一样,你可以直接使用dispatch_group_enterdispatch_group_leave使用GCD组。

在这个例子中,我们假装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!

并不是说其他答案在某些情况下不太好,但这是我经常从谷歌使用的一个片段:

- (void)runSigninThenInvokeSelector:(SEL)signInDoneSel {




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


}

上面的答案都很酷,但他们都忽略了一件事。当你使用dispatch_group_enter/dispatch_group_leave时,group在它进入的线程中执行任务(块)。

- (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来得到你想要的。

在Swift 5.1中,中央调度提供了许多方法来解决你的问题。根据您的需要,您可以选择下面Playground片段中显示的七个模式之一。


# 1。使用DispatchGroupDispatchGroupnotify(qos:flags:queue:execute:)DispatchQueueasync(group:qos:flags:execute:)

苹果开发者并发编程指南声明关于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。使用DispatchGroupDispatchGroupwait()DispatchGroupenter()DispatchGroupDispatchGroup0

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:)混合。


# 3。使用Dispatch​Work​Item​Flags barrierDispatchQueueasync(group:qos:flags:execute:)

Swift 4中央调度教程:第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。使用DispatchWorkItemDispatch​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。使用DispatchSemaphoreDispatchSemaphorewait()DispatchSemaphoresignal()

Soroush Khanlou在GCD手册的博客文章中写道:

使用信号量,我们可以阻塞一个线程任意的时间,直到从另一个线程发送信号。信号量,像GCD的其他部分一样,是线程安全的,它们可以从任何地方被触发。当有一个异步API需要同步化时,可以使用信号量,但不能修改它。

Apple Developer API Reference还给出了DispatchSemaphore init(value:​)初始化器的以下讨论:

当两个线程需要协调特定事件的完成时,为值传递零非常有用。传递大于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开发者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
*/

# 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
*/

Swift 4.2示例:

let group = DispatchGroup.group(count: 2)
group.notify(queue: DispatchQueue.main) {
self.renderingLine = false
// all groups are done
}
DispatchQueue.main.async {
self.renderTargetNode(floorPosition: targetPosition, animated: closedContour) {
group.leave()
// first done
}
self.renderCenterLine(position: targetPosition, animated: closedContour) {
group.leave()
// second done
}
}

接受的回答:

let group = DispatchGroup()


group.async(group: DispatchQueue.global(qos: .default), execute: {
// block1
print("Block1")
Thread.sleep(forTimeInterval: 5.0)
print("Block1 End")
})




group.async(group: DispatchQueue.global(qos: .default), execute: {
// block2
print("Block2")
Thread.sleep(forTimeInterval: 8.0)
print("Block2 End")
})


dispatch_group_notify(group, DispatchQueue.global(qos: .default), {
// block3
print("Block3")
})


// only for non-ARC projects, handled automatically in ARC-enabled projects.
dispatch_release(group)