web-dev-qa-db-ja.com

ディスパッチキュー内のタスクの実行を停止する方法は?

シリアルキューがある場合、メインスレッドから、すぐに実行を停止してすべてのタスクをキャンセルするように指示するにはどうすればよいですか?

52
Randall

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
17
adib

これは非常に一般的な質問であり、以前に回答したことがあります。

GCDクエリの問題の一時停止

簡単な答えは、GCDにはキャンセルAPIがないことです。自分でキャンセルコードを実装する必要があります。上記の私の答えでは、基本的にどのようにそれを行うことができるかを示しています。

14
Ryan

Swiftを使用している場合、DispatchWorkItemクラスにより、作業単位をキャンセルできます。個別に。

作業項目を使用すると、個々の作業単位のプロパティを直接構成できます。また、完了を待つ、完了について通知を受ける、および/またはそれらをキャンセルする目的で、個々のワークユニットに対処することもできます。 (iOS 8.0+ macOS 10.10+で使用可能)。

DispatchWorkItemは、実行可能な作業をカプセル化します。作業項目は、DispatchQueueおよびDispatchGroup内にディスパッチできます。 DispatchWorkItemは、DispatchSourceイベント、登録、またはキャンセルハンドラーとして設定することもできます。

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

11
l'L'l

実行中の現在のブロックを停止できるかどうかはわかりませんが、dispatch_suspendを呼び出して、キューが新しいキューアイテムを実行しないようにすることができます。その後、dispatch_resumeを呼び出して実行を再開することができます(しかし、それはあなたがやりたいことではないようです)。

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

4
dtuckernet

詳細

  • Xcodeバージョン10.2(10E125)、Swift 5

方法1. OperationQueue

操作オブジェクトをキャンセルすると、オブジェクトはキューに残りますが、できるだけ早くタスクを停止する必要があることをオブジェクトに通知します。現在実行中の操作の場合、これは、操作オブジェクトの作業コードがキャンセル状態を確認し、実行中の操作を停止し、自分自身を終了としてマークする必要があることを意味します

ソリューション

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

ソリューション

 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 QueueController

ここに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")
    }
}
3

NSOperationQueueの cancelAllOperations を参照してください。操作がキャンセルメッセージを正しく処理することを確認するのは、まだあなた次第です。

2
Micah Hainline

別の解決策は、古いキューを破棄して新しいキューを作成することです。わたしにはできる。配列を削除するようなものです。配列上のすべての要素を削除するか、新しい配列を作成して古い配列を置き換えることができます。

0
jack