在启动另一个块之前等待直到执行两个异步块

tom*_*tom 182 objective-c grand-central-dispatch ios objective-c-blocks

使用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
});
Run Code Online (Sandbox Code Playgroud)

Jör*_*ich 292

使用调度组:看到这里的一个例子,在苹果的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);
Run Code Online (Sandbox Code Playgroud)

并可以产生这样的输出:

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
Run Code Online (Sandbox Code Playgroud)

  • 随你(由你决定.`dispatch_group_async`就像`dispatch_async`,添加了一个组参数.因此,如果您对block1和block2使用不同的队列或在同一个并发队列上安排它们,它们可以并发运行; 如果您在同一个串行队列上安排它们,它们将按顺序运行.它与没有组的块安排没有什么不同. (9认同)
  • 凉.一旦与组关联,异步任务/块将按顺序执行还是同时执行?我的意思是,假设block1和block2现在与一个组关联,block2会等到block1完成才能开始执行吗? (3认同)
  • 在ARC中只需删除dispatch_release(group); (2认同)

ɲeu*_*urɳ 268

在乔恩Eyrich答案扩展(给予好评他的回答,如果你给予好评此一个),如果你没有在控制dispatch_async你的块调用,可能是异步完成块的情况下,您可以使用使用GCD组dispatch_group_enterdispatch_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!");
Run Code Online (Sandbox Code Playgroud)

在此示例中,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();
    });
}
Run Code Online (Sandbox Code Playgroud)

输出(来自运行的时间戳):

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!
Run Code Online (Sandbox Code Playgroud)

  • 好帖子进一步解释说:http://commandshift.co.uk/blog/2014/03/19/using-dispatch-groups-to-wait-for-multiple-web-services/ (3认同)
  • @cbartel,好抓!我已更新示例代码以反映您的评论.很多时候你需要将回调放在主队列上 - 在这种情况下,虽然`dispatch_queue_notify`可能更好(除非阻塞时间保证很短). (2认同)

Ima*_*tit 81

通过Swift 3,Grand Central Dispatch提供了许多方法来解决您的问题.根据您的需要,您可以选择以下Playground片段中显示的六种模式之一.


#1.使用DispatchGroup,DispatchGroup notify(qos:flags:queue:execute:)和方法DispatchQueue async(group:qos:flags:execute:)

Apple Developer并发编程指南说明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
 */
Run Code Online (Sandbox Code Playgroud)

#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
 */
Run Code Online (Sandbox Code Playgroud)

请注意,您也可以混合DispatchGroup wait()使用DispatchQueue async(group:qos:flags:execute:)或混合DispatchGroup enter()DispatchGroup leave()DispatchGroup notify(qos:flags:queue:execute:).


#3.使用属性和方法Dispatch?Work?Item?Flags barrierDispatchQueue async(group:qos:flags:execute:) DispatchWorkItem

Swift 3的Grand Central Dispatch教程: 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
 */
Run Code Online (Sandbox Code Playgroud)

#4.使用Dispatch?Work?Item?Flags,属性和方法barrier DispatchQueueasync(execute:) DispatchSemaphore

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
 */
Run Code Online (Sandbox Code Playgroud)

#5.使用DispatchSemaphore,wait() DispatchSemaphoresignal() DispatchSemaphore方法

Soroush Khanlou在The GCD Handbook博客文章中写了以下几行:

使用信号量,我们可以在任意时间内阻塞线程,直到发送来自另一个线程的信号.与GCD的其余部分一样,信号量是线程安全的,它们可以从任何地方触发.当需要进行同步的异步API时,可以使用信号量,但不能修改它.

Apple Developer API Reference还为init(value:?) OperationQueue初始化程序提供了以下讨论:

当两个线程需要协调特定事件的完成时,为值传递零是有用的.传递大于零的值对于管理有限的资源池非常有用,其中池大小等于该值.

用法:

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
 */
Run Code Online (Sandbox Code Playgroud)

#6.使用OperationaddDependency(_:)

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
 */
Run Code Online (Sandbox Code Playgroud)


Rob*_*Rob 58

另一个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");  
});
Run Code Online (Sandbox Code Playgroud)

只需创建一个并发队列,调度两个块,然后使用barrier调度最后一个块,这将使其等待其他两个块完成.


Rob*_*Rob 39

我知道你问过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];
Run Code Online (Sandbox Code Playgroud)

  • 当NSBlockOperation内部的代码是同步的时,这很好.但是,如果不是这样,并且您希望在异步操作完成时触发完成? (3认同)
  • @GregMaletic在这种情况下,我创建一个并发的`NSOperation`子类,并在异步过程完成时设置`isFinished`.然后依赖项工作正常. (3认同)