Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to stop the execution of tasks in a dispatch queue?

If I have a serial queue, how can I, from the main thread, tell it to immediately stop execution and cancel all of its tasks?

like image 436
Randall Avatar asked Aug 03 '11 15:08

Randall


People also ask

How do I cancel a dispatch work item?

A dispatch work item has a cancel flag. If it is cancelled before running, the dispatch queue won't execute it and will skip it. If it is cancelled during its execution, the cancel property return True. In that case, we can abort the execution.

How do I cancel a GCD task?

If you want to stop a timer, call timer. invalidate() to stop it. Create a new NSTimer when you want to start it again.

How do I cancel Dispatchqueue?

Voting is disabled while the site is in read-only mode. Voting is disabled while the site is in read-only mode. Bookmarking is disabled while the site is in read-only mode.

How does queue dispatch work?

Dispatch queues are FIFO queues to which your application can submit tasks in the form of block objects. Dispatch queues execute tasks either serially or concurrently. Work submitted to dispatch queues executes on a pool of threads managed by the system.


4 Answers

There is no way to empty pending tasks from a dispatch queue without implementing non-trivial logic yourself as of iOS 9 / OS X 10.11.

If you have a need to cancel a dispatch queue, you might be better off using NSOperationQueue which offers this and more. For example, here's how you "cancel" a queue:

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
like image 113
adib Avatar answered Oct 16 '22 11:10

adib


If you're using Swift the DispatchWorkItem class allows works units to be cancelled individually.

Work items allow you to configure properties of individual units of work directly. They also allow you to address individual work units for the purposes of waiting for their completion, getting notified about their completion, and/or canceling them. ( available for use in iOS 8.0+ macOS 10.10+ ).

DispatchWorkItem encapsulates work that can be performed. A work item can be dispatched onto a DispatchQueue and within a DispatchGroup. A DispatchWorkItem can also be set as a DispatchSource event, registration, or cancel handler.

↳ https://developer.apple.com/reference/dispatch/dispatchworkitem

like image 20
l'L'l Avatar answered Oct 16 '22 12:10

l'L'l


This is a pretty common question, and one I've answered before:

Suspending GCD query problem

The short answer is that GCD doesn't have a cancellation API; you have to implement your cancellation code yourself. In my answer, above, I show basically how that can be done.

like image 14
Ryan Avatar answered Oct 16 '22 11:10

Ryan


Details

  • Xcode Version 10.2 (10E125), Swift 5

Way 1. OperationQueue

  • OperationQueue
  • Operation cancel()
  • Operation and OperationQueue Tutorial in Swift

Canceling an operation object leaves the object in the queue but notifies the object that it should stop its task as quickly as possible. For currently executing operations, this means that the operation object’s work code must check the cancellation state, stop what it is doing, and mark itself as finished

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

  • DispatchWorkItem
  • DispatchWorkItem cancel()

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() }
}

Usage of base solution (full sample)

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

add code of DispatchWorkItemController here

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) }
    }
}

Usage of QueueController (full sample)

 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")
    }
}
like image 12
Vasily Bodnarchuk Avatar answered Oct 16 '22 12:10

Vasily Bodnarchuk