web-dev-qa-db-fra.com

Comment arrêter l'exécution de tâches dans une file d'attente de répartition?

Si j'ai une file d'attente série, comment puis-je, à partir du thread principal, lui dire d'arrêter immédiatement l'exécution et d'annuler toutes ses tâches?

52
Randall

Il n'y a aucun moyen de vider les tâches en attente d'une file d'attente de répartition sans implémenter vous-même une logique non triviale à partir d'iOS 9/OS X 10.11.

Si vous avez besoin d'annuler une file d'attente de répartition, vous feriez mieux d'utiliser NSOperationQueue qui offre cela et plus encore. Par exemple, voici comment "annuler" une file d'attente:

NSOperationQueue* queue = [NSOperationQueue new];
queue.maxConcurrentOperationCount = 1; // make it a serial queue

...
[queue addOperationWithBlock:...]; // add operations to it
...

// Cleanup logic. At this point _do not_ add more operations to the queue
queue.suspended = YES; // halts execution of the queue
[queue cancelAllOperations]; // notify all pending operations to terminate
queue.suspended = NO; // let it go.
queue=nil; // discard object
17
adib

C'est une question assez courante à laquelle j'ai déjà répondu:

Suspension du problème de requête GCD

La réponse courte est que GCD n'a pas d'API d'annulation; vous devez implémenter vous-même votre code d'annulation. Dans ma réponse, ci-dessus, je montre essentiellement comment cela peut être fait.

14
Ryan

Si vous utilisez Swift la classe DispatchWorkItem permet d'annuler les unités works individuellement.

Les éléments de travail vous permettent de configurer directement les propriétés des unités de travail individuelles. Ils vous permettent également de vous adresser à des unités de travail individuelles dans le but d'attendre leur achèvement, d'être informé de leur achèvement et/ou de les annuler. (disponible pour une utilisation dans iOS 8.0+ macOS 10.10+).

DispatchWorkItem encapsule le travail qui peut être effectué. Un élément de travail peut être envoyé sur une DispatchQueue et au sein d'un DispatchGroup. Un DispatchWorkItem peut également être défini comme un événement DispatchSource, un enregistrement ou un gestionnaire d'annulation.

https://developer.Apple.com/reference/dispatch/dispatchworkitem

11
l'L'l

Je ne sais pas si vous pouvez arrêter un bloc en cours d'exécution, mais vous pouvez appeler dispatch_suspend pour empêcher la file d'attente d'exécuter de nouveaux éléments de file d'attente. Vous pouvez ensuite appeler dispatch_resume pour redémarrer l'exécution (mais cela ne sonne pas comme si c'était ce que vous vouliez faire).

http://developer.Apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html

4
dtuckernet

Détails

  • Xcode version 10.2 (10E125), Swift 5

Voie 1. OperationQueue

L'annulation d'un objet opération laisse l'objet dans la file d'attente mais informe l'objet qu'il doit arrêter sa tâche le plus rapidement possible. Pour les opérations en cours d'exécution, cela signifie que le code de travail de l'objet d'opération doit vérifier l'état d'annulation, arrêter ce qu'il fait et se marquer comme terminé.

Solution

class ViewController: UIViewController {

    private lazy var queue = OperationQueue()
    override func viewDidLoad() {
        super.viewDidLoad()

        queue.addOperation(SimpleOperation(title: "Task1", counter: 50, delayInUsec: 100_000))
        queue.addOperation(SimpleOperation(title: "Task2", counter: 10, delayInUsec: 500_000))

        DispatchQueue   .global(qos: .background)
            .asyncAfter(deadline: .now() + .seconds(3)) { [weak self] in
                guard let self = self else { return }
                self.queue.cancelAllOperations()
                print("Cancel tasks")
        }
    }
}

class SimpleOperation: Operation {

    private let title: String
    private var counter: Int
    private let delayInUsec: useconds_t

    init(title: String, counter: Int, delayInUsec: useconds_t) {
        self.title = title
        self.counter = counter
        self.delayInUsec = delayInUsec
    }

    override func main() {
        if isCancelled { return }
        while counter > 0 {
            print("\(title), counter: \(counter)")
            counter -= 1
            usleep(delayInUsec)
            if isCancelled { return }
        }
    }
}

Way 2.1 DispatchWorkItemController

Solution

 protocol DispatchWorkItemControllerDelegate: class {
    func workСompleted(delegatedFrom controller: DispatchWorkItemController)
 }

 class DispatchWorkItemController {

    weak var delegate: DispatchWorkItemControllerDelegate?
    private(set) var workItem: DispatchWorkItem?
    private var semaphore = DispatchSemaphore(value: 1)
    var needToStop: Bool {
        get {
            semaphore.wait(); defer { semaphore.signal() }
            return workItem?.isCancelled ?? true
        }
    }

    init (block: @escaping (_ needToStop: ()->Bool) -> Void) {
        let workItem = DispatchWorkItem { [weak self] in
            block { return self?.needToStop ?? true }
        }
        self.workItem = workItem
        workItem.notify(queue: DispatchQueue.global(qos: .utility)) { [weak self] in
            guard let self = self else { return }
            self.semaphore.wait(); defer { self.semaphore.signal() }
            self.workItem = nil
            self.delegate?.workСompleted(delegatedFrom: self)
        }
    }

    func setNeedsStop() { workItem?.cancel() }
    func setNeedsStopAndWait() { setNeedsStop(); workItem?.wait() }
}

Utilisation de la solution de base (échantillon complet)

class ViewController: UIViewController {

    lazy var workItemController1 = { self.createWorkItemController(title: "Task1", counter: 50, delayInUsec: 100_000) }()
    lazy var workItemController2 = { self.createWorkItemController(title: "Task2", counter: 10, delayInUsec: 500_000) }()

    override func viewDidLoad() {
        super.viewDidLoad()

        DispatchQueue.global(qos: .default).async(execute: workItemController1.workItem!)
        DispatchQueue.global(qos: .default).async(execute: workItemController2.workItem!)

        DispatchQueue   .global(qos: .background)
                        .asyncAfter(deadline: .now() + .seconds(3)) { [weak self] in
                guard let self = self else { return }
                self.workItemController1.setNeedsStop()
                self.workItemController2.setNeedsStop()
                print("tasks canceled")
        }
    }

    private func createWorkItemController(title: String, counter: Int, delayInUsec: useconds_t) -> DispatchWorkItemController {
        let controller = DispatchWorkItemController { needToStop in
            var counter = counter
            while counter > 0 {
                print("\(title), counter: \(counter)")
                counter -= 1
                usleep(delayInUsec)
                if needToStop() { print("canceled"); return }
            }
        }
        controller.delegate = self
        return controller
    }
}

extension ViewController: DispatchWorkItemControllerDelegate {
    func workСompleted(delegatedFrom controller: DispatchWorkItemController) {
        print("-- work completed")
    }
}

Way 2.2 QueueController

ajoutez le code de DispatchWorkItemController ici

protocol QueueControllerDelegate: class {
    func tasksСompleted(delegatedFrom controller: QueueController)
}

class QueueController {

    weak var delegate: QueueControllerDelegate?
    private var queue: DispatchQueue
    private var workItemControllers = [DispatchWorkItemController]()
    private var semaphore = DispatchSemaphore(value: 1)
    var runningTasksCount: Int {
        semaphore.wait(); defer { semaphore.signal() }
        return workItemControllers.filter { $0.workItem != nil } .count
    }

    func setNeedsStopTasks() {
        semaphore.wait(); defer { semaphore.signal() }
        workItemControllers.forEach { $0.setNeedsStop() }
    }

    func setNeedsStopTasksAndWait() {
        semaphore.wait(); defer { semaphore.signal() }
        workItemControllers.forEach { $0.setNeedsStopAndWait() }
    }

    init(queue: DispatchQueue) { self.queue = queue }

    func async(block: @escaping (_ needToStop: ()->Bool) -> Void) {
        queue.async(execute: initWorkItem(block: block))
    }

    private func initWorkItem(block: @escaping (_ needToStop: ()->Bool) -> Void) -> DispatchWorkItem {
        semaphore.wait(); defer { semaphore.signal() }
        workItemControllers = workItemControllers.filter { $0.workItem != nil }
        let workItemController = DispatchWorkItemController(block: block)
        workItemController.delegate = self
        workItemControllers.append(workItemController)
        return workItemController.workItem!
    }
}

extension QueueController: DispatchWorkItemControllerDelegate {
    func workСompleted(delegatedFrom controller: DispatchWorkItemController) {
        semaphore.wait(); defer { semaphore.signal() }
        if let index = self.workItemControllers.firstIndex (where: { $0.workItem === controller.workItem }) {
            workItemControllers.remove(at: index)
        }
        if workItemControllers.isEmpty { delegate?.tasksСompleted(delegatedFrom: self) }
    }
}

Utilisation de QueueController (exemple complet)

 class ViewController: UIViewController {

    let queue = QueueController(queue: DispatchQueue(label: "queue", qos: .utility,
                                                     attributes: [.concurrent],
                                                     autoreleaseFrequency: .workItem,
                                                     target: nil))
    override func viewDidLoad() {
        super.viewDidLoad()
        queue.delegate = self
        runTestLoop(title: "Task1", counter: 50, delayInUsec: 100_000)
        runTestLoop(title: "Task2", counter: 10, delayInUsec: 500_000)

        DispatchQueue   .global(qos: .background)
            .asyncAfter(deadline: .now() + .seconds(3)) { [weak self] in
                guard let self = self else { return }
                print("Running tasks count: \(self.queue.runningTasksCount)")
                self.queue.setNeedsStopTasksAndWait()
                print("Running tasks count: \(self.queue.runningTasksCount)")
        }
    }

    private func runTestLoop(title: String, counter: Int, delayInUsec: useconds_t) {
        queue.async { needToStop in
            var counter = counter
            while counter > 0 {
                print("\(title), counter: \(counter)")
                counter -= 1
                usleep(delayInUsec)
                if needToStop() { print("-- \(title) canceled"); return }
            }
        }
    }
}

extension ViewController: QueueControllerDelegate {
    func tasksСompleted(delegatedFrom controller: QueueController) {
        print("-- all tasks completed")
    }
}
3
Vasily Bodnarchuk

Voir cancelAllOperations sur NSOperationQueue. C'est toujours à vous de vous assurer que vos opérations gèrent correctement le message d'annulation.

2
Micah Hainline

Une autre solution consiste à jeter l'ancienne file d'attente et à en créer une nouvelle. Ça marche pour moi. C'est comme supprimer un tableau, vous pouvez supprimer tous les éléments sur celui-ci ou vous pouvez simplement en créer un nouveau pour remplacer l'ancien.

0
jack