web-dev-qa-db-fra.com

Comment répartir sur la file d'attente principale de manière synchrone sans blocage?

J'ai besoin de répartir un bloc sur la file d'attente principale, de manière synchrone. Je ne sais pas si je suis actuellement en cours d'exécution sur le thread principal ou non. La solution naïve ressemble à ceci:

dispatch_sync(dispatch_get_main_queue(), block);

Mais si je suis actuellement dans un bloc en cours d'exécution sur la file d'attente principale, cet appel crée un blocage. (La répartition synchrone attend la fin du bloc, mais le bloc ne démarre même pas, car nous attendons la fin du bloc actuel.)

La prochaine étape évidente consiste à vérifier la file d'attente actuelle:

if (dispatch_get_current_queue() == dispatch_get_main_queue()) {
    block();
} else {
    dispatch_sync(dispatch_get_main_queue(), block);
}

Cela fonctionne, mais c'est moche. Avant de le cacher au moins derrière une fonction personnalisée, n’existe-t-il pas une meilleure solution à ce problème? Je souligne que je ne peux pas me permettre de répartir le bloc de manière asynchrone - l'application se trouve dans une situation où le bloc réparti de manière asynchrone serait exécuté "trop tard".

61
zoul

J'ai besoin d'utiliser quelque chose comme ça assez régulièrement dans mes applications Mac et iOS, donc j'utilise la fonction d'assistance suivante (initialement décrite dans cette réponse ):

void runOnMainQueueWithoutDeadlocking(void (^block)(void))
{
    if ([NSThread isMainThread])
    {
        block();
    }
    else
    {
        dispatch_sync(dispatch_get_main_queue(), block);
    }
}

que vous appelez via

runOnMainQueueWithoutDeadlocking(^{
    //Do stuff
});

C'est à peu près le processus que vous décrivez ci-dessus, et j'ai parlé à plusieurs autres développeurs qui ont eux-mêmes conçu quelque chose comme ça.

J'ai utilisé [NSThread isMainThread] Au lieu de vérifier dispatch_get_current_queue(), car la section des mises en garde pour cette fonction a une fois mis en garde contre cette utilisation pour les tests d'identité et l'appel a été déprécié sous iOS 6 .

68
Brad Larson

Pour la synchronisation sur la file d'attente principale ou sur le thread principal (ce n'est pas pareil) j'utilise:

import Foundation

private let mainQueueKey    = UnsafeMutablePointer<Void>.alloc(1)
private let mainQueueValue  = UnsafeMutablePointer<Void>.alloc(1)


public func dispatch_sync_on_main_queue(block: () -> Void)
{
    struct dispatchonce  { static var token : dispatch_once_t = 0  }
    dispatch_once(&dispatchonce.token,
    {
        dispatch_queue_set_specific(dispatch_get_main_queue(), mainQueueKey, mainQueueValue, nil)
    })

    if dispatch_get_specific(mainQueueKey) == mainQueueValue
    {
        block()
    }
    else
    {
        dispatch_sync(dispatch_get_main_queue(),block)
    }
}

extension NSThread
{
    public class func runBlockOnMainThread(block: () -> Void )
    {
        if NSThread.isMainThread()
        {
            block()
        }
        else
        {
            dispatch_sync(dispatch_get_main_queue(),block)
        }
    }

    public class func runBlockOnMainQueue(block: () -> Void)
    {
        dispatch_sync_on_main_queue(block)
    }
}
2
JollyJinx

J'ai récemment commencé à rencontrer un blocage lors des mises à jour de l'interface utilisateur. Cela m'a conduit à cette question de débordement de pile, qui m'a amené à implémenter une fonction d'assistance de type runOnMainQueueWithoutDeadlocking basée sur la réponse acceptée.

Le vrai problème, cependant, est que lors de la mise à jour de l'interface utilisateur à partir d'un bloc, j'avais utilisé par erreur dispatch_sync plutôt que dispatch_async pour obtenir la file d'attente principale pour les mises à jour de l'interface utilisateur. Facile à faire avec l'achèvement du code, et peut-être difficile à remarquer après coup.

Donc, pour les autres personnes qui lisent cette question: si l'exécution synchrone n'est pas requise, il suffit d'utiliser dispatch_**a**sync évitera l'impasse que vous pourriez rencontrer par intermittence.

0
pkamb