如何停止调度队列中任务的执行?

Posted

技术标签:

【中文标题】如何停止调度队列中任务的执行?【英文标题】:How to stop the execution of tasks in a dispatch queue? 【发布时间】:2011-10-19 05:10:43 【问题描述】:

如果我有一个串行队列,我如何从主线程告诉它立即停止执行并取消其所有任务?

【问题讨论】:

我这里是用例子回答的,你可以看看。 enter link description here 【参考方案1】:

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

【讨论】:

有一些新的api,查看dispatch_block_t,您可以使用dispatch_block_cancel取消一个块【参考方案2】:

如果您使用SwiftDispatchWorkItem 类允许单独取消工程单元。

工作项允许您直接配置各个工作单元的属性。它们还允许您处理单个工作单元,以等待其完成、收到有关其完成的通知和/或取消它们。 ( 适用于 iOS 8.0+ macOS 10.10+)。

DispatchWorkItem 封装了可以执行的工作。一个工作项 可以分派到 DispatchQueue 和 DispatchGroup 中。一种 DispatchWorkItem 也可以设置为 DispatchSource 事件, 注册,或取消处理程序。

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

【讨论】:

使用箭头的好主意:) 谢谢,这正是我想要的!知道为什么该课程的文档如此之少吗? DispatchWorkItem 不会取消它已经开始执行的工作项。如果DispatchQueue 尚未执行,取消一个只会停止未来的执行。 @shoe:DispatchWorkItem 将用作DispatchGroup 中的取消处理程序。这允许当前正在执行的操作不断检查其状态并根据handier 取消,这实际上将其置于完成状态,停止进一步执行。 OP 要求一种方法来停止当前正在执行的任务。您发布了对他们问题的答案,但这不是他们问题的解决方案。因此,在 this 上下文中,您的答案具有误导性。 DispatchWorkItem 取消正在执行的任务。 DispatchWorkItem 不提供该功能,即使它与 DispatchGroup 一起使用。【参考方案3】:

这是一个很常见的问题,我之前回答过一个问题:

Suspending GCD query problem

简短的回答是 GCD 没有取消 API;您必须自己实施取消代码。在上面的回答中,我基本上展示了如何做到这一点。

【讨论】:

【参考方案4】:

详情

Xcode 版本 10.2 (10E125),Swift 5

方式1.OperationQueue

OperationQueue Operation cancel() Operation and OperationQueue Tutorial in Swift

取消操作对象会使对象留在队列中,但会通知对象它应该尽快停止其任务。对于当前正在执行的操作,这意味着操作对象的工作代码必须检查取消状态,停止正在做的事情,并将自己标记为已完成

解决方案

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 
        
    


方式2.1 DispatchWorkItemController

DispatchWorkItem DispatchWorkItem cancel()

解决方案

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


方式2.2队列控制器

在此处添加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")
    

【讨论】:

【参考方案5】:

我不确定您是否可以停止当前正在执行的块,但您可以调用 dispatch_suspend 来阻止队列执行任何新的队列项。然后您可以调用 dispatch_resume 来重新开始执行(但听起来这不是您想要做的)。

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

【讨论】:

【参考方案6】:

请参阅 NSOperationQueue 上的 cancelAllOperations。您仍然需要确保您的操作正确处理取消消息。

【讨论】:

【参考方案7】:

在尝试解决我自己的类似问题时,我找到了解决此类问题的有趣方法。基本概念是,无论什么类调用调度,它都有一个 id 属性来跟踪某些方法的当前执行,对我来说,它正在打开一个警报视图。调用 dispatch 的方法然后保存一个生成的 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】:

另一种解决方案是丢弃旧队列并创建一个新队列。这个对我有用。这就像删除一个数组,您可以删除其中的每个元素,也可以简单地创建一个新的来替换旧的。

【讨论】:

但是你是怎么做到的?? 我不知道你是怎么做到的,但我认为这行不通。您必须释放队列,但队列上的每个块都保留对它的引用,因此在所有块完成之前实际上不会从内存中清除它。 tl;dr 这将导致大量内存泄漏。【参考方案9】:

今天早些时候正在解决一个类似的问题,如果用户在完成之前导航离开,我想放弃为视图控制器加载数据所涉及的任务。基本上,我最终确定的方法是在 DispatchQueue 执行的闭包中使用对控制器的弱引用,并编写代码以在它消失时优雅地失败。

【讨论】:

以上是关于如何停止调度队列中任务的执行?的主要内容,如果未能解决你的问题,请参考以下文章

ios多线程同步异步、串行并行队列、死锁

Laravel的队列和任务调度的区别

第09章下 任务调度

yarn三种调度器(资源调度策略或机制)

如何在 Dask 中停止正在运行的任务?

python-celery专注于实现分布式异步任务处理任务调度的插件!