4
votes

Processus d'annulation Swift DispatchQueue

J'ai une méthode UDP qui attend une réponse à l'aide de DispatchQueue en utilisant le code suivant:

DispatchQueue.global(qos: .userInitiated).async {
    let server:UDPServer=UDPServer(address:"0.0.0.0", port:5005)
    let (data,_,_) = server.recv(1024)
    DispatchQueue.main.async {
       ...
    }
}

Cela fonctionne parfaitement et déclenche un processus pour attendre l'arrivée de mes données. me garder éveillé la nuit, que se passe-t-il si nous n’obtenons jamais de réponse? server.recv ne revient jamais donc je ne peux pas voir comment le processus se terminera un jour? Existe-t-il un moyen de lui donner une durée de fonctionnement prédéterminée?


6 commentaires

voir la discussion dans ce post: stackoverflow.com/questions/38105105/...


Il n'a pas de fonction de temporisation intégrée que je connaisse. La seule chose à laquelle je peux penser est d'utiliser cancel () et de vérifier la propriété isCancelled depuis l'intérieur du rappel. Cependant, cela ne fonctionne pas si recv est bloquant.


Comment UDPServer est-il implémenté? À quoi ressemble recv ? Sans voir le code de blocage réel, il est difficile de vous donner des suggestions.


J'utilise SwiftSockets.


J'ai jeté un œil aux SwiftSockets , il semble que ce que vous voulez n'est pas possible, car SwiftSockets ne fournit pas la fonctionnalité spécifiée dans stackoverflow.com/questions/13547721/udp-socket-set-timeout


Si SwiftSockets ne prend pas en charge les délais d'expiration, vous pourrez peut-être simuler un faux délai d'expiration en vous envoyant des données UDP. J'ai mis à jour ma réponse.


3 Réponses :


3
votes

Ajoutez une minuterie qui se déclenche après un intervalle de temps spécifique

Déclarez une constante pour l'intervalle de temps et une propriété pour la minuterie

fileprivate func startDispatchTimer()
{
    let interval : DispatchTime = .now() + .seconds(timeoutSeconds)
    if timer == nil {
        timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
        timer!.schedule(deadline:interval)
        timer!.setEventHandler {
            // do something when the timer fires
            self.timer = nil
        }
        timer!.resume()
    }
}

fileprivate func stopDispatchTimer()
{
    timer?.cancel()
    timer = nil
}

et écrivez deux fonctions pour démarrer et arrêtez le minuteur.

private let timeoutSeconds = 30
private var timer : DispatchSourceTimer?

Démarrez le minuteur après l'initialisation de l'instance de serveur et arrêtez-le en cas de succès. En cas d'échec, ajoutez du code dans la fermeture setEventHandler pour gérer le délai d'expiration, par exemple en désallouant l'instance de serveur.


9 commentaires

Mais comment annuleriez-vous réellement le DispatchWorkItem qui s'exécute actuellement en arrière-plan? Vous ne pouvez pas arrêter directement un DispatchWorkItem, et comme server.recv ne revient jamais, vous ne pouvez pas non plus vérifier s'il a été annulé.


@wvteijlingen Utilisez ensuite Operation et OperationQueue


Pourtant, vous ne pouvez pas arrêter une opération de l'extérieur. Vous pouvez l'annuler, mais cela ne définira que l'indicateur isCancelled sur true. C'est toujours le travail de l'opération elle-même de vérifier périodiquement cet indicateur et d'arrêter de s'exécuter s'il est défini. Puisque recv est bloquant, il n'y a aucun moyen de vérifier l'indicateur isCancelled pendant l'exécution.


@wvteijlingen Je ne suis pas sûr de bien vous comprendre. Si vous démarrez le chronomètre avant d'appeler recv , le gestionnaire d'événements appellera cancelAllOperations () sur la file d'attente des opérations s'il expire. La file d'attente globale n'est qu'un exemple. Vous pouvez créer votre propre file d'attente pour le minuteur.


Vous pouvez appeler cancelAllOperations () , mais cela n'arrêtera aucune opération en cours. Extrait de la documentation: "L'annulation des opérations ne les supprime pas automatiquement de la file d'attente ni n'arrête celles qui sont en cours d'exécution. Pour les opérations qui sont déjà en cours d'exécution, l'objet opération lui-même doit vérifier l'annulation et arrêter ce qu'il fait pour pouvoir se déplacer. à l'état fini. " ( developer.apple.com/documentation/foundation/operationqueue‌ /… ). Donc, si le minuteur se déclenche lorsque la méthode recv est bloquée, il n'y a aucun moyen d'arrêter l'exécution.


@wvteijlingen Re: "Vous ne pouvez pas arrêter directement un DispatchWorkItem", enfin, peut-être pas "directement", mais un peu indirectement. Avez-vous vu cette réponse ? Le code annule le DispatchWorkItem sans problème.


@ l'L'l: Oui, je sais que vous pouvez appeler cancel () sur un DispatchWorkItem . Cependant, cela définit simplement la propriété isCancelled sur true. C'est toujours le travail de l'élément de travail lui-même d'arrêter de s'exécuter. En ce sens, vous ne pouvez pas arrêter ou "tuer" un élément de travail. Vous ne pouvez lui demander de s'arrêter que par lui-même. Cela ne fonctionne pas dans le cas d'OP.


@wvteijlingen: Mettre une boucle while dans l'élément de travail qui vérifie le statut booléen est possible d'après ce que j'ai observé; si isCancelled est vrai, alors retournez et le fil est tué - c'est ainsi que je l'ai fait fonctionner, à moins que ce ne soit pas le problème réel.


@ l'L'l: Normalement, cela fonctionnerait. Dans ce cas cependant, recv ne revient jamais, rendant une boucle while inutile.



4
votes

Il n'y a aucun moyen d'arrêter ou de "tuer" un DispatchWorkItem ou NSOperation de l'extérieur. Il existe une méthode cancel () , mais qui définit simplement la propriété isCancelled de l'élément ou de l'opération sur true. Cela n'interrompt pas l'exécution de l'élément lui-même. Réponse puisque recv est bloquant, il n'y a aucun moyen de vérifier l'indicateur isCancelled pendant l'exécution. Cela signifie que la réponse publiée par Vadian ne ferait malheureusement rien.

D'après la documentation Apple sur NSOperation. annuler :

Cette méthode ne force pas l'arrêt de votre code d'opération.

Il en va de même pour NSOperationQueue.cancelAllOperations < / code>:

L'annulation des opérations ne les supprime pas automatiquement de la file d'attente ou n'arrête pas celles qui sont en cours d'exécution.

Vous pourriez penser qu'il est possible de passer à l'utilisation d'un NSThread brut. Cependant, le même principe s'applique hier. Vous ne pouvez pas tuer de manière déterministe un fil de l'extérieur.

Solution possible: délai d'attente

La meilleure solution à laquelle je puisse penser est d'utiliser la fonction timeout du socket. Je ne sais pas d'où vient UDPServer , mais il a peut-être un délai d'expiration intégré.

Solution possible: timeout du pauvre homme (envoyer le paquet à localhost)

Une autre option que vous pouvez essayer est de vous envoyer des paquets UDP après un certain temps. De cette façon, recv recevra des données et l'exécution se poursuivra. Cela pourrait éventuellement être utilisé comme "timeout du pauvre homme".


0 commentaires

0
votes

Où ne devrait pas être le cas où UDPServer ne retournera pas. Il devrait y avoir une limite de délai d'attente pour UDPServer. Par exemple, considérez le cas ci-dessous:

 DispatchQueue.global(qos: .background).async {
 let server = UDPServer(address:"0.0.0.0", port:5005)
    switch server.recv(1024) {
        case .success:
            print("Server received message from client.")
        case .failure(let error):
            print("Server failed to received message from client: \(error)")
        }

        server.close()
        DispatchQueue.main.async {
            ...
        }
    }


2 commentaires

Mais comment définir le délai d'expiration?


J'utilise SwiftSockets mais je ne vois pas de délai du côté UDP?