首页 文章

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

提问于
浏览
174

使用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 回答

  • -2

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


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

    Apple Developer并发编程指南states about 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:) 混合或混合 DispatchGroup enter()DispatchGroup leave()DispatchGroup notify(qos:flags:queue:execute:) .


    #3 . 使用DispatchWorkItemFlags barrier属性和DispatchQueue DispatchQueue async(group:qos:flags:execute :)方法

    来自Raywenderlich.com的Grand Central Dispatch Tutorial for Swift 3: Part 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 . 使用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()方法

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

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

    Apple Developer API Reference还为 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
     */
    
  • 3

    使用调度组:请参阅here以获取示例,"Dispatch Queues"在Apple的"Dispatch Queues"章节中's iOS Developer Library' s并发编程指南

    您的示例可能如下所示:

    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
    
  • 55

    扩展JörnEyrich的答案(如果你赞成这个答案,请回答他的答案),如果你无法控制你的块的 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!
    
  • 39

    另一个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调度最后一个块,这将使其等待其他两个块完成 .

  • 44

    我知道你问过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];
    
  • 258

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

    上面的答案都很酷,但他们都错过了一件事 . group在您使用 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 中运行 . 如果我不创建任何队列,它将在 main thread 中运行 .

    - (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 来获得你想要的东西 .

  • 2

    不是说其他答案在某些情况下并不是很好,但这是我一直在谷歌用户的一个片段:

    - (void)runSigninThenInvokeSelector:(SEL)signInDoneSel {
    
    
        if (signInDoneSel) {
            [self performSelector:signInDoneSel];
        }
    
    }
    

相关问题