web-dev-qa-db-fra.com

Files d'attente de répartition: comment savoir si elles sont en cours d'exécution et comment les arrêter

Je joue avec GCD et j'ai écrit une application CoinFlipper jouet.

Voici la méthode qui retourne les pièces:

- (void)flipCoins:(NSUInteger)nFlips{

    // Create the queues for work
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL);

    // Split the number of flips into whole chunks of kChunkSize and the remainder.
    NSUInteger numberOfWholeChunks = nFlips / kChunkSize;
    NSUInteger numberOfRemainingFlips = nFlips - numberOfWholeChunks * kChunkSize;

    if (numberOfWholeChunks > 0) {
        for (NSUInteger index = 0; index < numberOfWholeChunks; index++) {
            dispatch_async(queue, ^{
                NSUInteger h = 0;
                NSUInteger t = 0;
                flipTheCoins(kChunkSize, &h, &t);
                dispatch_async(mainQueue, ^{
                    self.nHeads += h;
                    self.nTails += t;
                });
            });
        }
    }
    if (numberOfRemainingFlips > 0) {
        dispatch_async(queue, ^{
            NSUInteger h = 0;
            NSUInteger t = 0;
            flipTheCoins(numberOfRemainingFlips, &h, &t);
            dispatch_async(mainQueue, ^{
                self.nHeads += h;
                self.nTails += t;
            });
        });

    }
}

Comme vous pouvez le voir; Je divise le nombre de flips en gros morceaux en les retournant en arrière-plan et en mettant à jour les propriétés dans la file d'attente principale. Les propriétés sont observées par le contrôleur de fenêtre et une interface utilisateur est mise à jour avec les résultats en cours d'exécution.

J'ai parcouru le Guide de programmation des accès concurrents et les documents GCD, et bien qu'il existe un moyen de suspendre une file d'attente, il n'y a aucun moyen de les arrêter et de supprimer tous les objets en file d'attente et non en cours d'exécution.

J'aimerais pouvoir brancher un bouton "stop" pour annuler le retournement une fois qu'il a commencé. Avec NSOperationQueue je peux observer la propriété operationCount pour savoir si elle est en cours d'exécution et cancelAllOperations pour supprimer les blocs en file d'attente.

J'ai parcouru le Guide de programmation des accès concurrents et les documents GCD, et bien qu'il existe un moyen de suspendre une file d'attente, il n'y a aucun moyen de les arrêter et de supprimer tous les objets en file d'attente et non en cours d'exécution.

Alors :-

  1. Comment savoir si les blocs que j'ai ajoutés à une file d'attente sont toujours en attente?
  2. Comment annuler des blocs qui ne sont pas encore exécutés?
  3. Je suis nouveau dans les trucs GCD, alors je le fais bien?
33
Abizern

Il s'agit d'une question semi-courante lors de la programmation avec GCD.

La réponse courte est que GCD n'a pas d'API d'annulation pour les files d'attente. La justification:

  1. la gestion de la mémoire deviendrait beaucoup plus compliquée, car un bloc donné pourrait être responsable de la libération () d'une allocation donnée de mémoire. En exécutant toujours le bloc, GCD garantit que la gestion de la mémoire reste simple.
  2. Il est pratiquement impossible d'arrêter un bloc en cours d'exécution sans altérer l'état.
  3. La plupart des codes nécessitant une logique d'annulation suivent déjà cet état dans les structures de données privées.

Dans tous ces cas, il est beaucoup plus efficace et puissant d'écrire du code comme ceci:

dispatch_async(my_obj->queue, ^{
    bool done = false;
    // do_full_update() takes too long, therefore:
    while ( !my_obj->cancelled && !done ) {
        done = do_partial_update(my_obj);
    }
});

Oh, et pour savoir si une file d'attente a terminé d'exécuter tous les blocs mis en file d'attente, votre code peut simplement exécuter un bloc vide avec l'API synchrone:

dispatch_sync(my_obj->queue, ^{});

Comme mentionné dans les commentaires, une meilleure façon de savoir quand votre travail est terminé est d'utiliser des groupes de répartition. Envoyez tous vos blocs au groupe, puis vous pouvez ajouter un gestionnaire d'achèvement au groupe. Une fois le travail terminé, le bloc d'achèvement sera exécuté.

dispatch_group_t myGroup = dispatch_group_create();
dispatch_group_async(myGroup, my_obj->queue, ^{
    bool done = false;
    while ( !my_obj->cancelled && !done ) {
        done = do_partial_update(my_obj);
    }
});
dispatch_group_notify(myGroup, my_obj->queue, ^{
    NSLog(@"Work is done!");
    dispatch_release(myGroup);
});

Une fois tous vos blocs terminés, le groupe sera vide et déclenchera le bloc de notification. De là, vous pouvez mettre à jour l'interface utilisateur, etc.

Bonne chance et amusez-vous bien!

49
Anonymous

Comment savoir si fonctionne

BOOL dispatch_queue_is_empty(dispatch_queue_t queue)
{
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        dispatch_group_leave(group);
    });

    int64_t maxWaitTime = 0.00000005 * NSEC_PER_SEC;
    BOOL isReady = dispatch_group_wait(group, maxWaitTime) == 0;

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        dispatch_release(group);
    });

    return isReady;
}

Pour tester dans l'application

dispatch_queue_t queue = dispatch_queue_create("test", 0);

NSLog(@"Is empty %@", dispatch_queue_is_empty(queue) ? @"YES" : @"NO");

dispatch_async(queue, ^{
    for(int i = 0; i < 100; i++)
    {
        NSLog(@"... %i", i);
    }
});

NSLog(@"Is empty %@", dispatch_queue_is_empty(queue) ? @"YES" : @"NO");

Résultat

Is empty YES
Is empty NO
... 0
... 1
... 2
... 3
... 4
... 5
... 6
... 7
... 8
... 9

La valeur par défaut de la variable maxWaitTime peut être modifiée pour obtenir le résultat souhaité.

6
hfossli

Si vous avez une file d'attente de répartition série OR une file d'attente de répartition simultanée, voici un code qui peut faire la même chose.

BOOL __block queueIsEmpty = false;
dispatch_barrier_async (_dispatchQueue, ^{
    queueIsEmpty = true;
});

while (!queueIsEmpty) {
    int i = 0;  // NOOP instruction
}

// At this point your queue should be empty.
1
Kris Subramanian