等待直到执行了两个异步块,然后再启动另一个块


192

使用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
});

请参阅对Swift 5的回答,它提供了多达六种不同的方法来解决您的问题。
Imanou Petit

Answers:


301

使用调度组:请参见此处的示例,《 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

3
凉。与该组相关联的异步任务/块将被顺序执行还是同时执行?我的意思是,假设block1和block2现在与一个组相关联,block2将等到block1完成后才能开始执行吗?
汤姆(Tom)2012年

9
随你(由你决定。dispatch_group_async就像dispatch_async添加了一个组参数一样。因此,如果您对block1和block2使用不同的队列,或者将它们安排在同一并发队列中,则它们可以并发运行。如果您将它们安排在相同的串行队列中,它们将顺序运行。这与调度没有组的块没有什么不同。
约恩·埃里希(JörnEyrich)2012年

1
这也适用于执行Web服务发布吗?
SleepNot

您是否注意到时间不等于您在模块中设置的睡眠时间?为什么会这样呢?
戴蒙(Damon Yuan)

2
在ARC中,只需删除dispatch_release(group);即可。
loretoparisi

272

扩展JörnEyrich答案(如果支持此答案,则支持他的答案),如果您无法控制对dispatch_async块的调用(如异步完成块的情况),则可以使用dispatch_group_enterdispatch_group_leave直接使用GCD组。

在此示例中,我们假装computeInBackground无法更改的东西(假设它是委托回调,NSURLConnectioncompleteHandler或其他任何东西),因此我们无权访问调度调用。

// 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!

1
@ɲeuroburɳ上面的代码在主线程上等待。我相信这将阻塞主线程,并导致UI在整个组完成之前无响应。我建议将等待移到后台线程。例如,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0)
cbartel 2014年

2
@cbartel,赶上!我已经更新了示例代码以反映您的评论。很多时候,您都需要将回调放在主队列中,在这种情况下,尽管回调dispatch_queue_notify可能会更好(除非可以保证阻塞时间很短)。
ɲeuroburɳ

我在哪里可以释放该组(即dispatch_release(group))?我不确定在dispatch_group_notify中释放是否安全。但是,由于那是在组完成之后运行的代码,因此我不确定在哪里发布。
GingerBreadMane 2015年

:如果您正在使用ARC,那么你不需要调用dispatch_release stackoverflow.com/questions/8618632/...
ɲeuroburɳ

3
尼斯后进一步解释说:commandshift.co.uk/blog/2014/03/19/...
Rizon

96

使用Swift 5.1,Grand Central Dispatch提供了许多解决问题的方法。根据您的需要,您可以选择以下Playground片段中显示的七个模式之一。


#1 使用DispatchGroupDispatchGroupnotify(qos:flags:queue:execute:)DispatchQueueasync(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
 */

#2。使用DispatchGroupDispatchGroupwait()DispatchGroupenter()DispatchGroupleave()

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的Grand Central Dispatch教程: Raywenderlich.com的1/2部分文章给出了障碍的定义:

调度屏障是一组在处理并发队列时充当串行样式瓶颈的功能。当您将a提交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参考还针对以下内容进行了讨论 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。使用OperationQueueOperationaddDependency(_:)

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

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

有没有一种异步调用的解决方案,而无需为每个调用使用group.enter()和group.leave()(并且没有信号量)?就像如果我需要等待对服务器的异步请求,那么在那之后等待第二个异步请求,依此类推。我已阅读avanderlee.com/swift/asynchronous-operations的这篇文章,但与BlockOperation相比,我看不到它的简单用法
Woof

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

只需创建一个并发队列,分派您的两个块,然后分派带有屏障的最后一个块,这将使其等待其他两个块完成。


如果我不使用sleep(4),有什么问题吗?
Himanth

不,当然,这没有问题。实际上,您几乎从不想要sleep()!我仅sleep()出于教学原因添加了这些调用,以使这些块运行足够长的时间,以便您可以看到它们同时运行。在这个简单的示例中,如果没有sleep(),这两个块可能运行得如此之快,以至于在您有机会凭经验观察并发执行之前,分派的块可能会开始和结束。但是不要使用sleep()自己的代码。
罗布(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];

3
当NSBlockOperation内部的代码是同步的时,这很好。但是如果不是这样,并且您想在异步操作完成后触发完成?
Greg Maletic

3
@GregMaletic在这种情况下,我将创建一个NSOperation并发的子类,并isFinished在异步过程完成时进行设置。然后,依赖项可以正常工作。
罗布


1
@GregMaletic是的,您也可以使用它(只要dispatch_semaphore_wait没有在主队列中发生,并且只要您的信号和等待平衡即可)。只要您不阻塞主队列,就可以使用信号量方法,如果您不需要操作的灵活性(例如,可以取消操作,控制并发程度等)。
罗布(Rob)2013年

1
@ Reza.Ab-如果需要任务一在任务二开始之前完成,请在这些任务之间添加依赖项。或者,如果队列始终一次仅执行一项任务,请通过设置maxConcurrentOperationCount为使其成为串行队列1。您可以设置优先级业务的,也无论是qualityOfServicequeuePriority,但这些具有比依赖性和/或队列并发度的任务优先级的更为微妙的影响。
罗布

4

上面的答案都很酷,但是都错过了一件事。当您使用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来获得所需的东西。


3

第一个答案本质上是正确的,但是如果您想以最简单的方式完成所需的结果,下面是一个独立的代码示例,演示如何使用信号量(这也是调度组在幕后的工作方式,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();
}

7
有两个发现:1.您缺少dispatch_semaphore_wait。您有两个信号,因此需要两次等待。照原样,您的“ completion”块将在第一个块发出信号后立即开始,但在另一个块完成之前开始;2.鉴于这是一个iOS问题,所以我不鼓励使用dispatch_main
罗布

1
我同意罗布。这不是有效的解决方案。dispatch_semaphore_wait任一dispatch_semaphore_signal方法被调用时,will将立即解除阻塞。看起来有效的原因是,printffor块“ one”和“ two”立即发生,而printffor“ finally”出现在等待之后-因此,在一个块睡了2秒钟之后。如果将printf放在sleep调用之后,则输出将为“ 1”,然后在2秒钟后为“ finally”,然后在2秒后为“ two”。
ɲeuroburɳ

1

快速接受答案:

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)

0

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
    }
 }

group.leave()造成崩溃

-3

更不用说其他答案在某些情况下也不是很好,但这是我一直从Google使用的一小段代码:

- (void)runSigninThenInvokeSelector:(SEL)signInDoneSel {


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

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.