Wachten tot twee asynchrone blokken worden uitgevoerd vóór het begin van een ander blok

Bij het gebruik van GCD, willen we wachten tot twee asynchrone blokken worden uitgevoerd en gedaan voordat u naar de volgende stappen van de uitvoering. Wat is de beste manier om dat te doen?

We hebben geprobeerd de volgende, maar het lijkt niet te werken:

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

Antwoord 1, Autoriteit 100%

Met dispatch groepen: zie hier voor een voorbeeld, “Wachten op groepen van de wachtrij Taken” in de “Dispatch Wachtrijen” hoofdstuk van Apple’s iOS Developer Library Concurrency Programming Guide

Uw voorbeeld zou er ongeveer zo uitzien:

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

en kon de uitgang als volgt te produceren:

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

Antwoord 2, gezag 89%

Als uitbreiding op Jörn Eyrich antwoord (upvote zijn antwoord als u deze upvote), als je geen controle over de dispatch_asyncGesprekken voor uw blokken, zoals het geval voor asynchrone voltooiing blokken, kunt u de GCD groepen gebruiken met dispatch_group_enteren dispatch_group_leavedirect.

In dit voorbeeld gaan we doen alsof computeInBackgroundis iets wat we niet kunnen veranderen (voorstellen dat het een afgevaardigde callback, NSURLConnection completionHandler, of wat dan ook), en dus we hebben geen toegang tot de verzending hebben gesprekken.

// 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!");

In dit voorbeeld wordt computeInBackground:completion: geïmplementeerd als:

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

Uitvoer (met tijdstempels van een run):

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!

Antwoord 3, autoriteit 38%

Met Swift 5.1 biedt Grand Central Dispatchvele manieren om uw probleem op te lossen. Afhankelijk van uw behoeften kunt u een van de zeven patronenkiezen die worden weergegeven in de volgende Playground-fragmenten.


#1. Met behulp van DispatchGroup, DispatchGroup‘ s notify(qos:flags:queue:execute:)en DispatchQueue‘s async(group:qos:flags:execute:)

De Apple Developer Concurrency Programming Guide stelt over DispatchGroup:

Verzendgroepen zijn een manier om een ​​thread te blokkeren totdat een of meer taken zijn uitgevoerd. U kunt dit gedrag gebruiken op plaatsen waar u geen vooruitgang kunt boeken totdat alle gespecificeerde taken zijn voltooid. Nadat u bijvoorbeeld verschillende taken hebt verzonden om bepaalde gegevens te berekenen, kunt u een groep gebruiken om op die taken te wachten en de resultaten te verwerken wanneer ze klaar zijn.

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. Met behulp van DispatchGroup, DispatchGroup‘ s wait(), DispatchGroup‘s enter()en DispatchGroup‘s 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
 */

Merk op dat u DispatchGroupwait()ook kunt combineren met DispatchQueueasync(group:qos:flags:execute:)of mix DispatchGroupenter()en DispatchGroupleave()met DispatchGroupnotify(qos:flags:queue:execute:).


#3. Dispatch​Work​Item​Flagsbarrieren DispatchQueue‘s async(group:qos:flags:execute:)

Grand Central Dispatch-zelfstudie for Swift 4: Part 1/2artikel van Raywenderlich.com geeft een definitie voor barrières:

Verzendbarrières zijn een groep functies die fungeren als een seriële bottleneck bij het werken met gelijktijdige wachtrijen. Wanneer u een DispatchWorkItemnaar een verzendwachtrij verzendt, kunt u vlaggen instellen om aan te geven dat dit het enige item moet zijn dat voor die specifieke tijd in de opgegeven wachtrij moet worden uitgevoerd. Dit betekent dat alle items die vóór de verzendbarrière in de wachtrij zijn geplaatst, moeten zijn voltooid voordat het DispatchWorkItemwordt uitgevoerd.

Gebruik:

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. Met behulp van DispatchWorkItem, Dispatch​Work​Item​Flags‘s barrieren DispatchQueue‘s 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. Met behulp van DispatchSemaphore, DispatchSemaphore‘ s wait()en DispatchSemaphore‘s signal()

Soroush Khanlou schreef de volgende regels in de blogpost The GCD Handbook:

Met behulp van een semafoor kunnen we een thread voor een willekeurige tijd blokkeren, totdat een signaal van een andere thread wordt verzonden. Semaforen zijn, net als de rest van GCD, thread-safe en kunnen overal worden geactiveerd. Semaforen kunnen worden gebruikt wanneer er een asynchrone API is die u synchroon moet maken, maar u kunt deze niet wijzigen.

Apple Developer API Reference geeft ook de volgende discussie voor DispatchSemaphoreinit(value:​)initialisatie:

Nul doorgeven voor de waarde is handig wanneer twee threads de voltooiing van een bepaalde gebeurtenis met elkaar moeten verzoenen. Het doorgeven van een waarde groter dan nul is handig voor het beheren van een eindige pool van resources, waarbij de poolgrootte gelijk is aan de waarde.

Gebruik:

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. Met behulp van OperationQueueen Operation‘ s addDependency(_:)

In de Apple Developer API Reference staat over Operation​Queue:

Bewerkingswachtrijen gebruiken de libdispatch-bibliotheek (ook bekend als Grand Central Dispatch) om de uitvoering van hun bewerkingen te starten.

Gebruik:

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. Met behulp van OperationQueueen OperationQueue‘ s addBarrierBlock(_:)(vereist 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
 */

Antwoord 4, autoriteit 18%

Een ander GCD-alternatief is een barrière:

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

Maak gewoon een gelijktijdige wachtrij, verzend uw twee blokken en verzend vervolgens het laatste blok met een barrière, waardoor het wacht tot de andere twee klaar zijn.


Antwoord 5, autoriteit 13%

Ik weet dat je naar GCD hebt gevraagd, maar als je wilt, behandelt NSOperationQueuedit soort dingen ook heel gracieus, bijvoorbeeld:

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];

Antwoord 6

De bovenstaande antwoorden zijn allemaal leuk, maar ze hebben allemaal één ding gemist. group voert taken (blokken) uit in de thread waar het is ingevoerd wanneer u dispatch_group_enter/dispatch_group_leavegebruikt.

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

dit wordt uitgevoerd in de aangemaakte gelijktijdige wachtrij demoQueue. Als ik geen wachtrij aanmaak, wordt deze uitgevoerd in hoofdthread.

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

en er is een derde manier om taken in een andere thread uit te voeren:

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

Natuurlijk kun je, zoals vermeld, dispatch_group_asyncgebruiken om te krijgen wat je wilt.


Antwoord 7

Het eerste antwoord is in wezen correct, maar als u de eenvoudigste manier wilt om het gewenste resultaat te bereiken, is hier een op zichzelf staand codevoorbeeld dat laat zien hoe u dit kunt doen met een semafoor (wat ook is hoe verzendgroepen achter de schermen werken , 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();
}

Antwoord 8

Swift 4.2 voorbeeld:

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

Antwoord 9

Geaccepteerd antwoord snel:

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)

Antwoord 10

Om niet te zeggen dat andere antwoorden niet geweldig zijn voor bepaalde omstandigheden, maar dit is een fragment dat ik altijd van Google gebruik:

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

Other episodes