4 votes

Utiliser DispatchSemaphore dans ce scénario de traitement spéculatif "commence à être occupé".

Imaginez un écran S. Les utilisateurs arrivent à S, regardent des choses. Il y a un bouton B ...

|    |
|   B|
|    |
|    |

Lorsque vous appuyez sur B .

func clickedB() {

   blockingSpinner = true
   longCalculation()
   blockingSpinner = false
   showResult()
}

func longCalculation() {

   // a few seconds
}

(Nous voulons que l'utilisateur attende simplement, en voyant un spinner modal, si/pendant que le calcul est en cours).

Généralement, lorsqu'un utilisateur arrive sur l'écran S, il regarde autre chose pendant quelques secondes avant de toucher B.

Alors...

var waitor = DispatchSemaphore(value: 0) // or ???

func viewDidLoad() {

   DispatchQueue.global(qos: .background).async { longCalculation() }
}
func longCalculation() {

   something waitor
   do the calculation
   something waitor
   DispatchQueue.main.async {
     something waitor
   }
}
func clickedB() {

   // (note that ...   calculation may have finished ages ago
   // or we may be in the middle of it, it has a second or so remaining
   // or perhaps even this is the second+ time the user has clicked B)
   something waitor
   if/while longCalculation is still running,
       blockingSpinner = true
   blockingSpinner = false
   showResult()
}

J'ai peur de n'avoir aucune idée de comment utiliser DispatchSemaphore dans ce scénario.

La façon particulière dont ils ont fait wait() y signal() le travail ne semble pas s'additionner ici.

Mode d'emploi DispatchSemaphore dans ce scénario ?

1voto

Josh Caswell Points 40397

Vous voulez qu'une seule chose puisse procéder à la fois, donc la valeur de votre sémaphore devrait être 1. Compte tenu de cela, vous pourriez tout aussi bien utiliser un sémaphore de type NSLock . Mais voici les grandes lignes de l'utilisation du sémaphore.

Lorsque vous commencez le calcul, wait() (indéfiniment) sur le sémaphore. Comme vous l'avez suggéré, vous pouvez profiter de l'ordre inhérent au cycle de vie du contrôleur de vue pour savoir que cela ne bloquera pas réellement. Dès que vous avez terminé, `signal(). Évidemment, tout ceci doit être fait en arrière-plan afin que le thread principal ne soit pas bloqué.

Lorsque vous traitez l'appui sur le bouton, testez le sémaphore en le faisant "attendre" avec un délai d'attente instantané, et affichez le compteur si vous obtenez .timedOut en conséquence. Nous pouvons faire cela sur le thread principal, car que le sémaphore soit disponible ou non, il n'y aura pas de temps d'attente réel. Notez qu'aucun signal n'est nécessaire ici : si le sémaphore en attente s'éteint le sémaphore est réincrémenté automatiquement. Si l'attente réussit, alors le travail est terminé - passez directement à l'affichage du résultat.

Si le travail n'est pas terminé, vous montrez le spinner ; maintenant attendez (indéfiniment) en arrière-plan. Lorsque cette attente prend fin -- parce que le sémaphore a été signalé -- revenez au fil principal, renvoyez le compteur et présentez les résultats.

À ce stade, le compte du sémaphore est à 0, donc si vous avez besoin de repasser par ce chemin de code, vous devez signaler le sémaphore.

En gros, remplir le croquis du code que vous avez fait :

class WhateverViewController : UIViewController
{
    private let semaphore = DispatchSemaphore(value: 1)

    override func viewDidLoad()
    {
        super.viewDidLoad()
        self.performLongCalculation()
    }

    private func performLongCalculation()
    {
        DispatchQueue.global(qos: .background).async {
            self.semaphore.wait()
            // Synchronous processing...
            self.semaphore.signal()
        }
    }

    private func buttonTapped()
    {
        if self.semaphore.isBusy {
            self.waitForResult()
        }
        else {
            self.showResult()
        }
    }

    private func buttonTappedAlternative()
    {
        // Show the spinner unconditionally, if you assume that the
        // calculation isn't already done.
        self.waitForResult()
    }

    private func waitForResult()
    {
        self.showSpinner()
        DispatchQueue.global(qos: .userInitiated).async {
            self.semaphore.wait()
            DispatchQueue.main.async {
                self.dismissSpinner()
                self.showResult()
            }
        }
    }

    private func showResult()
    {
        // Put stuff on screen
        self.semaphore.signal()
    }
}

buttonTapped utilise une commodité sur DispatchSemaphore

extension DispatchSemaphore
{
    var isBusy : Bool { return self.wait(timeout: .now()) == .timedOut }
}

Et vous pouvez inverser cette logique si vous préférez : isIdle serait juste self.wait(timeout: .now()) == .success

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