How to stop the execution of tasks in a dispatch queue?
Solution 1
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
Solution 2
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
Solution 3
This is a pretty common question, and one I've answered before:
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.
Solution 4
Details
- Xcode Version 10.2 (10E125), Swift 5
Way 1. OperationQueue
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
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")
}
}
Solution 5
I'm not sure if you can stop a current block that is executing, but you can call dispatch_suspend to prevent the queue from executing any new queue items. You can then call dispatch_resume to restart execution (but it doesn't sound like that is what you want to do).
Randall
Updated on November 26, 2020Comments
-
Randall over 3 years
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?
-
Fattie over 8 yearsbut how do you do that ??
-
phreakhead almost 8 yearsI don't know how you do this, but I don't think that will work. You have to release the queue, but each block on the queue retains a reference to it, so it won't actually be cleared from memory until all the blocks complete. tl;dr this will cause massive memory leaks.
-
Vyacheslav about 7 yearsnice idea to use arrow:)
-
Rob almost 6 yearsThank you, this is exactly what I was looking for! Any idea why the docs for that class are so minimal?
-
Lukewcn over 4 yearsThere's some new apis, check out
dispatch_block_t
, you can usedispatch_block_cancel
to cancel a block -
shoe almost 4 years
DispatchWorkItem
will not cancel its work item if it has already begun execution. Cancelling one will only stop future execution if theDispatchQueue
has yet to execute it. -
l'L'l almost 4 years@shoe: The
DispatchWorkItem
would be used as a cancel handler within aDispatchGroup
. This allows the operation that is currently executing to continually check its status and cancelled per the handier, which essentially puts it in a finished state, stopping further execution. -
shoe over 3 yearsThe OP asked for a way to stop a currently executing task. You posted an answer to their question, but it is not a solution to their problem. So in this context your answer is misleading.
DispatchWorkItem
will not cancel an executing task. That functionality is not provided byDispatchWorkItem
, even if it is used withDispatchGroup
. -
l'L'l over 3 years@shoe, When I answered this question four years ago it worked fine, so perhaps Apple changed something along the way or you aren’t understanding how to implement it correctly.