web-dev-qa-db-fra.com

dispatch_sync sur la file d'attente principale se bloque dans le test unitaire

J'éprouvais quelques difficultés à tester un grand code de répartition central avec le framework de test d'unité Xcode intégré, SenTestingKit. J'ai réussi à faire bouillir mon problème. J'ai un test unitaire qui construit un bloc et essaie de l'exécuter sur le thread principal. Cependant, le bloc n'est jamais réellement exécuté, donc le test se bloque car c'est une répartition synchrone.

- (void)testSample {

    dispatch_sync(dispatch_get_main_queue(), ^(void) {
        NSLog(@"on main thread!");
    });

    STFail(@"FAIL!");
}

Qu'est-ce que l'environnement de test fait que cela se bloque?

43
Drewsmits

dispatch_sync exécute un bloc sur une file d'attente donnée et attend qu'il se termine. Dans ce cas, la file d'attente est la file d'attente principale de répartition. La file d'attente principale exécute toutes ses opérations sur le thread principal, dans l'ordre FIFO (premier entré, premier sorti). Cela signifie que chaque fois que vous appelez dispatch_sync, votre nouveau bloc sera placé à la fin de la ligne, et ne s'exécutera que lorsque tout le reste avant qu'il soit terminé dans la file d'attente.

Le problème ici est que le bloc que vous venez de mettre en file d'attente est à la fin de la ligne en attente d'exécution sur le thread principal - tandis que le testSample méthode est en cours d'exécution sur le thread principal. Le bloc à la fin de la file d'attente ne peut pas accéder au thread principal tant que la méthode actuelle (elle-même) n'a pas terminé en utilisant le thread principal. Cependant dispatch_sync signifie Soumet un objet bloc pour exécution dans une file d'attente de répartition et attend la fin de ce bloc.

111
BJ Homer

Le problème dans votre code est que peu importe que vous utilisiez dispatch_sync Ou dispatch_async, STFail() sera toujours appelé, ce qui entraînera l'échec de votre test.

Plus important encore, comme l'a expliqué BJ Homer, si vous devez exécuter quelque chose de manière synchrone dans la file d'attente principale, vous devez vous assurer que vous n'êtes pas dans la file d'attente principale ou un blocage se produira. Si vous êtes dans la file d'attente principale, vous pouvez simplement exécuter le bloc comme une fonction régulière.

J'espère que cela t'aides:

- (void)testSample {

    __block BOOL didRunBlock = NO;
    void (^yourBlock)(void) = ^(void) {
        NSLog(@"on main queue!");
        // Probably you want to do more checks here...
        didRunBlock = YES;
    };

    // 2012/12/05 Note: dispatch_get_current_queue() function has been
    // deprecated starting in iOS6 and OSX10.8. Docs clearly state they
    // should be used only for debugging/testing. Luckily this is our case :)
    dispatch_queue_t currentQueue = dispatch_get_current_queue();
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

    if (currentQueue == mainQueue) {
        blockInTheMainThread();
    } else {
        dispatch_sync(mainQueue, yourBlock); 
    }

    STAssertEquals(YES, didRunBlock, @"FAIL!");
}
8
nacho4d

Si vous êtes sur la file d'attente principale et attendez de façon synchrone que la file d'attente principale soit disponible, vous attendez en effet longtemps. Vous devez tester pour vous assurer que vous n'êtes pas déjà sur le thread principal.

6
NJones

Sortirez-vous un jour de la maison si vous devez attendre pour que vous sortiez d'abord de la maison? Vous avez bien deviné! Non! :]

Fondamentalement si:

  1. Vous êtes sur FooQueue. (ne doit pas nécessairement être main_queue)
  2. Vous appelez la méthode en utilisant sync c'est-à-dire de manière série et souhaitez exécuter sur FooQueue.

Cela n'arrivera jamais pour la même raison que vous ne sortirez jamais de chez vous!

Il ne sera jamais expédié car il doit attendre pour sortir de la file d'attente!

3
Honey

A suivre, depuis

dispatch_get_current_queue()

est désormais obsolète, vous pouvez utiliser

[NSThread isMainThread]

pour voir si vous êtes sur le fil principal.

Donc, en utilisant l'autre réponse ci-dessus, vous pourriez faire:

- (void)testSample 
{
    BOOL __block didRunBlock = NO;
    void (^yourBlock)(void) = ^(void) {
        NSLog(@"on main queue!");
        didRunBlock = YES;
    };

    if ([NSThread isMainThread])
        yourBlock();
    else
        dispatch_sync(dispatch_get_main_queue(), yourBlock);

    STAssertEquals(YES, didRunBlock, @"FAIL!");
}
2
codebaboon