web-dev-qa-db-fra.com

Est-ce que dispatch_async (dispatch_get_main_queue (), ^ {...}); attendre d'avoir terminé?

J'ai un scénario dans mon application, dans lequel je veux effectuer une tâche fastidieuse qui consiste en un traitement de données ainsi qu'une mise à jour de l'interface utilisateur, dans une méthode. Ma méthode ressemble à ceci,

- (void)doCalculationsAndUpdateUIs {

    // DATA PROCESSING 1
    // UI UPDATE 1

    // DATA PROCESSING 2
    // UI UPDATE 2

    // DATA PROCESSING 3
    // UI UPDATE 3
} 

Comme cela prend beaucoup de temps, je voulais faire le traitement des données sur le fil d’arrière-plan, en utilisant,

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{

Mais comme le traitement des données et les mises à jour de l'interface utilisateur sont dans la même méthode, je voulais déplacer uniquement les mises à jour de l'interface utilisateur dans le fil principal à l'aide de,

dispatch_async(dispatch_get_main_queue(), ^{

Enfin ma méthode ressemble à ceci,

- (void)doCalculationsAndUpdateUIs {

    // DATA PROCESSING 1 
    dispatch_async(dispatch_get_main_queue(), ^{
        // UI UPDATE 1
    });

    /* I expect the control to come here after UI UPDATE 1 */

    // DATA PROCESSING 2
    dispatch_async(dispatch_get_main_queue(), ^{
        // UI UPDATE 2
    });

    /* I expect the control to come here after UI UPDATE 2 */

    // DATA PROCESSING 3
    dispatch_async(dispatch_get_main_queue(), ^{
        // UI UPDATE 3
    });
}

Est-ce que ça marche vraiment? Est-ce vraiment une bonne pratique? Quel est le meilleur moyen d'y parvenir?

P.S. Ces trois opérations sont interdépendantes.


EDIT: Désolé les gars. J'ai manqué une ligne dans le code ci-dessus . Mon code actuel ressemble à ceci.

- (void)doCalculationsAndUpdateUIs {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // DATA PROCESSING 1 
        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATE 1
        });

        /* I expect the control to come here after UI UPDATE 1 */

        // DATA PROCESSING 2
        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATE 2
        });

        /* I expect the control to come here after UI UPDATE 2 */

        // DATA PROCESSING 3
        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATE 3
        });
    });
}

Encore une fois, je m'excuse vraiment pour la confusion.

41
EmptyStack

Non, ça n'attend pas et la façon dont vous le faites dans cet échantillon n'est pas une bonne pratique.

dispatch_async est toujours asynchrone . Vous mettez simplement en file d'attente tous les blocs de l'interface utilisateur dans la même file d'attente afin que les différents blocs s'exécutent en séquence, mais parallèlement au code de traitement de vos données. 

Si vous souhaitez que la mise à jour attende, vous pouvez utiliser dispatch_sync à la place.

// This will wait to finish
dispatch_sync(dispatch_get_main_queue(), ^{
    // Update the UI on the main thread.
});

Une autre approche consisterait à imbriquer la mise en file d'attente du bloc. Je ne voudrais pas le recommander pour plusieurs niveaux cependant. 

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // Background work

    dispatch_async(dispatch_get_main_queue(), ^{
        // Update UI

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // Background work

            dispatch_async(dispatch_get_main_queue(), ^{
                // Update UI
            });
        });
    });
});

Si vous avez besoin que l'interface utilisateur soit mise à jour pour attendre, vous devez utiliser les versions synchrones. C'est assez correct d'avoir un fil de fond qui attend le fil principal. Les mises à jour de l'interface utilisateur devraient être très rapides.

101
David Rönnqvist

Vous devez placer la répartition de votre file d'attente principale dans le bloc qui exécute le calcul. Par exemple (ici, je crée une file d’attribution et n’en utilise pas une globale):

dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL);
dispatch_async(queue, ^{
  // Do some computation here.

  // Update UI after computation.
  dispatch_async(dispatch_get_main_queue(), ^{
    // Update the UI on the main thread.
  });
});

Bien sûr, si vous créez une file d'attente, n'oubliez pas de dispatch_release si vous ciblez une version iOS antérieure à 6.0.

10
StatusReport

Votre doCalculationsAndUpdateUIs proposée effectue le traitement des données et envoie les mises à jour de l'interface utilisateur à la file d'attente principale. Je suppose que vous avez envoyé doCalculationsAndUpdateUIs dans une file d'attente en arrière-plan lorsque vous l'avez appelée pour la première fois. 

Bien que techniquement correct, c'est un peu fragile, vous devez vous rappeler de l'envoyer à l'arrière-plan chaque fois que vous l'appelez: je vous suggérerais plutôt de faire votre envoi à l'arrière-plan et de le renvoyer à l'intérieur de la même file d'attente. méthode, car elle rend la logique sans ambiguïté et plus robuste, etc. 

Ainsi, cela pourrait ressembler à:

- (void)doCalculationsAndUpdateUIs {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{

        // DATA PROCESSING 1 

        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATION 1
        });

        /* I expect the control to come here after UI UPDATION 1 */

        // DATA PROCESSING 2

        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATION 2
        });

        /* I expect the control to come here after UI UPDATION 2 */

        // DATA PROCESSING 3

        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATION 3
        });
    });
}

Indiquez si vous distribuez vos mises à jour d'interface utilisateur de manière asynchrone avec dispatch_async (où le processus d'arrière-plan attend pas attendra la mise à jour de l'interface utilisateur) ou de manière synchrone avec dispatch_sync (où il sera attendre la mise à jour de l'interface ), la question est pourquoi voudriez-vous le faire de manière synchrone: voulez-vous vraiment ralentir le processus d'arrière-plan pendant qu'il attend la mise à jour de l'interface utilisateur ou souhaitez-vous que le processus d'arrière-plan continue pendant que La mise à jour de l'interface utilisateur a lieu. 

En règle générale, vous enverriez la mise à jour de l'interface utilisateur de manière asynchrone avec dispatch_async, comme vous l'avez utilisé dans votre question d'origine. Oui, il existe certainement des circonstances particulières dans lesquelles vous devez envoyer le code de manière synchrone (par exemple, vous synchronisez les mises à jour d'une propriété de classe en effectuant toutes ses mises à jour dans la file d'attente principale), mais le plus souvent, vous envoyez juste la mise à jour de l'interface utilisateur. de manière asynchrone et continuez. La distribution du code de manière synchrone peut causer des problèmes (par exemple, des blocages) si elle est effectuée négligemment. Mon conseil général est que vous devriez probablement envoyer les mises à jour de l'interface utilisateur de manière synchrone si cela est absolument nécessaire. Sinon, vous devez concevoir votre solution de manière à pouvoir les envoyer de manière asynchrone. .


En réponse à votre question sur le "meilleur moyen d'y parvenir", il est difficile pour nous de dire sans en savoir plus sur le problème commercial en cours de résolution. Par exemple, si vous appelez cette doCalculationsAndUpdateUIs plusieurs fois, je serais peut-être enclin à utiliser ma propre file d'attente série plutôt qu'une file d'attente globale simultanée, afin de garantir que celles-ci ne se superposent pas. Ou si vous pouvez avoir besoin de la capacité d'annuler cette doCalculationsAndUpdateUIs lorsque l'utilisateur supprime la scène ou appelle à nouveau la méthode, je pourrais être enclin à utiliser une file d'attente d'opérations offrant des fonctionnalités d'annulation. Cela dépend entièrement de ce que vous essayez d'atteindre.

Mais, en général, il est très courant de répartir de manière asynchrone une tâche compliquée dans une file d'attente en arrière-plan, puis de renvoyer de manière asynchrone la mise à jour de l'interface utilisateur dans la file d'attente principale.

8
Rob

Si vous souhaitez exécuter une seule opération en file d'attente indépendante indépendante et que vous n'êtes pas concerné par d'autres opérations simultanées, vous pouvez utiliser la file d'attente simultanée globale:

dispatch_queue_t globalConcurrentQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

Cela renverra une file d'attente simultanée avec la priorité donnée, comme indiqué dans la documentation:

DISPATCH_QUEUE_PRIORITY_HIGH Les éléments envoyés à la file d'attente fonctionneront en priorité haute, c'est-à-dire que la file d'attente sera programmée pour être exécutée avant toute file d'attente à priorité basse ou à priorité basse.

DISPATCH_QUEUE_PRIORITY_DEFAULT Les éléments envoyés à la file d'attente fonctionneront avec la priorité par défaut, c'est-à-dire que la file d'attente sera planifiée pour être exécutée une fois que toutes les files d'attente à priorité élevée auront été planifiées, mais avant toutes les files d'attente à priorité basse.

DISPATCH_QUEUE_PRIORITY_LOW Les éléments envoyés à la file d'attente fonctionneront avec une priorité faible, c'est-à-dire que la file d'attente sera planifiée pour être exécutée une fois que toutes les files d'attente à priorité par défaut et à priorité élevée auront été planifiées.

DISPATCH_QUEUE_PRIORITY_BACKGROUND Les éléments envoyés à la file d'attente fonctionneront avec une priorité d'arrière-plan. En d'autres termes, la file d'attente sera planifiée pour une exécution après la planification de toutes les files d'attente de priorité supérieure et le système exécutera les éléments de cette file d'attente sur un thread dont l'état d'arrière-plan est défini par setpriority (2) ( c'est-à-dire que les E/S du disque sont limitées et que la priorité de planification du thread est définie sur la valeur la plus basse). 

2
Ashokios
dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL);
dispatch_async(queue, ^{
  // Do some computation here.

  // Update UI after computation.
  dispatch_async(dispatch_get_main_queue(), ^{
    // Update the UI on the main thread.
  });
});
1
Moin Shirazi

Non, ça n'attendra pas.

Vous pouvez utiliser performSelectorOnMainThread:withObject:waitUntilDone:.

1
Wain

La bonne pratique est la suivante: Groupes d’expédition

dispatch_group_t imageGroup = dispatch_group_create();

dispatch_group_enter(imageGroup);
[uploadImage executeWithCompletion:^(NSURL *result, NSError* error){
    // Image successfully uploaded to S3
    dispatch_group_leave(imageGroup);
}];

dispatch_group_enter(imageGroup);
[setImage executeWithCompletion:^(NSURL *result, NSError* error){
    // Image url updated
    dispatch_group_leave(imageGroup);
}];

dispatch_group_notify(imageGroup,dispatch_get_main_queue(),^{
    // We get here when both tasks are completed
});
0
ChavirA