如果我有一个串行队列,我怎么能从主线程告诉它立即停止执行并取消所有任务?
答案 0 :(得分:14)
这是一个非常常见的问题,我之前已经回答过:
简短的回答是GCD没有取消API;你必须自己实施你的取消代码。在上面的答案中,我基本上展示了如何做到这一点。
答案 1 :(得分:14)
从iOS 9 / OS X 10.11开始,没有办法从调度队列清空待处理任务而不自己实现非平凡逻辑。
如果您需要取消调度队列,最好使用提供此功能的NSOperationQueue
以及更多内容。例如,这里是你如何取消"一个队列:
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
答案 2 :(得分:10)
如果您正在使用Swift
DispatchWorkItem
类,则可以单独取消工作单位。
工作项允许您直接配置各个工作单元的属性。它们还允许您处理单个工作单元,以便等待完成,获得完成通知和/或取消它们。 (可在iOS 8.0+中使用 macOS 10.10+)。
DispatchWorkItem封装了可以执行的工作。一个工作项目 可以分派到DispatchQueue和DispatchGroup中。一个 DispatchWorkItem也可以设置为DispatchSource事件, 注册或取消处理程序。
↳https://developer.apple.com/reference/dispatch/dispatchworkitem
答案 3 :(得分:4)
我不确定您是否可以停止正在执行的当前块,但是您可以调用dispatch_suspend来阻止队列执行任何新的队列项。然后,您可以调用dispatch_resume重新启动执行(但听起来并不像您想要的那样)。
答案 4 :(得分:2)
请参阅NSOperationQueue上的cancelAllOperations。您仍然需要确保您的操作正确处理取消消息。
答案 5 :(得分:1)
取消操作对象会将对象保留在队列中,但通知该对象它应尽快停止其任务。对于当前正在执行的操作,这意味着操作对象的工作代码必须检查取消状态,停止正在执行的操作并将其标记为已完成
解决方案
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 }
}
}
}
解决方案
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() }
}
基本解决方案的使用(完整示例)
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")
}
}
在 DispatchWorkItemController 此处添加代码
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) }
}
}
QueueController的使用(完整示例)
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")
}
}
答案 6 :(得分:0)
另一个解决方案是丢弃旧队列并创建一个新队列。这个对我有用。它就像删除一个数组,你可以删除它上面的每个元素,或者你可以简单地创建一个新元素来替换它。
答案 7 :(得分:0)
在尝试解决自己的类似问题时,我发现了一种有趣的解决方案。基本概念是,无论什么类调用调度,它都有一个id属性,该属性跟踪某个方法的当前执行,对我而言,它正在打开警报视图。然后,调用分派的方法将保留生成的id的局部变量。如果ID尚未更改,那么我知道不会取消我的回调。如果已更改,则不要采取任何措施,因为其他警报已得到控制:
class AlertData: ObservableObject {
static var shared = AlertData()
@Published var alertOpen = false
@Published var alertMessage = ""
@Published var alertTitle = ""
var id: UUID = UUID()
func openAlert() {
// ID is used to only dismiss the most recent version of alert within timeout.
let myID = UUID()
self.id = myID
withAnimation {
self.alertOpen = true
}
DispatchQueue.main.asyncAfter(deadline: (.now() + 2), execute: {
// Only dismiss if another alert has not appeared and taken control
if self.id == myID {
withAnimation {
self.alertOpen = false
}
}
})
}
func closeAlert() {
withAnimation {
self.alertOpen = false
}
}
}
答案 8 :(得分:0)
今天早些时候正在解决一个类似的问题,如果用户要在完成之前导航离开,我想放弃为视图控制器加载数据所涉及的任务。基本上,我最终解决的方法是在DispatchQueue
执行的闭包中使用对控制器的弱引用,并编写代码以使其消失后自动失败。