184 votes

Comment dois-je attendre pour un mode asynchrone distribué bloc à la fin?

Je suis en train de tester un code qui fait le traitement asynchrone à l'aide de Grand Central Dispatch. Les tests de code ressemble à ceci:

[object runSomeLongOperationAndDo:^{
    STAssert…
}];

Les tests doivent attendre la fin de l'opération. Ma solution actuelle ressemble à ceci:

__block BOOL finished = NO;
[object runSomeLongOperationAndDo:^{
    STAssert…
    finished = YES;
}];
while (!finished);

Qui ressemble un peu brut, connaissez-vous une meilleure façon? J'ai pu exposer la file d'attente, puis bloc en appelant dispatch_sync:

[object runSomeLongOperationAndDo:^{
    STAssert…
}];
dispatch_sync(object.queue, ^{});

...mais c'est peut-être exposer trop sur l' object.

312voto

kperryua Points 6905

Essayez d'utiliser un dispatch_sempahore. Il devrait ressembler à quelque chose comme ceci:

dispatch_semaphore_t sema = dispatch_semaphore_create(0);

[object runSomeLongOperationAndDo:^{
    STAssert…

    dispatch_semaphore_signal(sema);
}];

dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);

Ce doit comporter correctement même si runSomeLongOperationAndDo: décide que l'opération n'est en fait pas assez longtemps à le mérite d'enfilage et s'exécute de façon synchrone à la place.

31voto

Rob Points 70987

En plus du sémaphore technique couverts de manière exhaustive dans d'autres réponses, nous pouvons maintenant utiliser XCTest dans Xcode 6 pour effectuer des tests asynchrones via XCTestExpectation. Ceci élimine le besoin pour les sémaphores lors des tests de code asynchrone. Par exemple:

- (void)testDataTask
{
    XCTestExpectation *expectation = [self expectationWithDescription:@"asynchronous request"];

    NSURL *url = [NSURL URLWithString:@"http://www.apple.com"];
    NSURLSessionTask *task = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        XCTAssertNil(error, @"dataTaskWithURL error %@", error);

        if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
            NSInteger statusCode = [(NSHTTPURLResponse *) response statusCode];
            XCTAssertEqual(statusCode, 200, @"status code was not 200; was %d", statusCode);
        }

        XCTAssert(data, @"data nil");

        // do additional tests on the contents of the `data` object here, if you want

        // when all done, Fulfill the expectation

        [expectation fulfill];
    }];
    [task resume];

    [self waitForExpectationsWithTimeout:10.0 handler:nil];
}

Pour le plaisir des futurs lecteurs, tandis que l'envoi de sémaphore technique est une merveilleuse technique lorsque cela est absolument nécessaire, je dois avouer que je vois beaucoup trop de nouveaux développeurs, peu familier avec bon asynchrone des modèles de programmation, gravitent autour trop vite sur les sémaphores comme un mécanisme général pour asynchrone à des routines de se comporter de manière synchrone. Le pire que j'ai vu beaucoup d'entre eux utilisent ce sémaphore de la technique à partir de la file d'attente principale (et il ne faut jamais bloquer la file d'attente principale dans la production d'applications).

Je sais que ce n'est pas le cas ici (lorsque cette question a été posté, il n'y avait pas un bon outil, comme XCTestExpectation; aussi, dans ces suites de tests, nous devons nous assurer que le test n'est pas fini jusqu'à ce que l'appel asynchrone est fait). C'est un de ces rares situations où le sémaphore technique pour bloquer le thread principal peut être nécessaire.

Donc, avec mes excuses à l'auteur de cette question d'origine, pour qui le sémaphore est la technique du son, j'écris cet avertissement à tous ces nouveaux développeurs qui voient ce sémaphore technique et d'envisager de l'appliquer dans leur code comme une approche générale pour traiter avec des méthodes asynchrones: Être avertis que, neuf fois sur dix, le sémaphore technique n'est pas la meilleure approche quand encounting les opérations asynchrones. Au lieu de cela, vous familiariser avec l'achèvement de bloc/fermeture des motifs, ainsi que des délégué-protocole de motifs et de notifications. Ceux-ci sont souvent bien meilleures façons de composer avec les tâches asynchrones, plutôt que d'utiliser des sémaphores pour les faire se comporter de façon synchrone. Habituellement, il ya de bonnes raisons que les tâches asynchrones ont été conçus pour se comporter de manière asynchrone, afin d'utiliser le bon modèle asynchrone plutôt que d'essayer de les faire se comporter de façon synchrone.

29voto

zoul Points 51637

J'ai récemment de nouveau ce problème et a écrit ce qui suit catégorie sur NSObject:

@implementation NSObject (Testing)

- (void) performSelector: (SEL) selector
    withBlockingCallback: (dispatch_block_t) block
{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self performSelector:selector withObject:^{
        if (block) block();
        dispatch_semaphore_signal(semaphore);
    }];
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_release(semaphore);
}

@end

De cette façon, je peux facilement tourner appel asynchrone avec un rappel dans un synchrone, l'un dans les tests:

[testedObject performSelector:@selector(longAsyncOpWithCallback:)
    withBlockingCallback:^{
    STAssert…
}];

25voto

Barry Points 569

En général, il n'utilise aucune de ces réponses, souvent, ils ne seront pas à l'échelle (il ya des exceptions ici et là, bien sûr)

Ces approches sont incompatibles avec la façon dont le PGCD est destiné à fonctionner et finit par causer des blocages et/ou de tuer la batterie en continu des bureaux de vote.

En d'autres termes, réorganiser votre code, de sorte qu'il n'est pas synchrone attente d'un résultat, mais au lieu de traiter avec un résultat notification de changement d'état (par exemple, des rappels/délégué protocoles, être disponible, s'en aller, les erreurs, etc.). (Ceux-ci peuvent être remaniée en blocs si vous n'aimez pas de rappel de l'enfer.) Parce que c'est la façon d'exposer le réel le comportement du reste de l'application de la cacher derrière une fausse façade.

Au lieu de cela, utiliser NSNotificationCenter, de définir un délégué personnalisé protocole avec des rappels pour votre classe. Et si vous n'aimez pas coucher avec délégué des rappels de partout, les envelopper dans un béton de classe de proxy qui implémente le protocole personnalisé et enregistre les différents bloc de propriétés. Probablement aussi fournir la commodité des constructeurs.

La première est un travail un peu plus, mais il permettra de réduire le nombre de terrible course-des conditions et de la batterie, le meurtre d'interrogation dans le long terme.

(Ne demandez pas pour un exemple, parce que c'est trivial et nous avons dû investir du temps pour apprendre l'objective-c bases de trop.)

6voto

Oliver Atkinson Points 2876
- (void)performAndWait:(void (^)(dispatch_semaphore_t semaphore))perform;
{
  NSParameterAssert(perform);
  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
  perform(semaphore);
  dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  dispatch_release(semaphore);
}

Exemple d'utilisation:

[self performAndWait:^(dispatch_semaphore_t semaphore) {
  [self someLongOperationWithSuccess:^{
    dispatch_semaphore_signal(semaphore);
  }];
}];

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X