如何确保OperationQueue中的操作一个接一个地完成

Tac*_*aco 4 concurrency nsoperation nsoperationqueue ios swift

当执行彼此依赖的操作时,OperationQueue可以使用它们来确保它们以正确的顺序执行.但是,是否也可以确保操作一个接一个地完成?

让我们假设一个异步执行的方法,需要一些时间才能完成:

public func performOperation(_ number: Int, success: @escaping (Int) -> Void)->Void {
        DispatchQueue(label: "operations").async {
            print("Operation #\(number) starts")
            usleep(useconds_t(1000-number*200)) // Block thread for some time
            success(number)
        }
}
Run Code Online (Sandbox Code Playgroud)

操作和依赖关系创建如下:

let operationQueue = OperationQueue.main
for operationNumber in 0..<4 { // Create operations as an example
    let operation = BlockOperation(block: {
        performOperation(operationNumber) { number in
            DispatchQueue.main.sync {
                print("Operation #\(number) finished")
            }
        }
    })
    operation.name = "Operation #\(operationNumber)"

    if operationNumber > 0 {
        operation.addDependency(operationQueue.operations.last!)
        // Print dependencies
        print("\(operation.name!) should finish after \(operation.dependencies.first!.name!)")
    }
    operationQueue.addOperation(operation)
}
Run Code Online (Sandbox Code Playgroud)

使用以下输出:

Operation #1 should finish after Operation #0
Operation #2 should finish after Operation #1
Operation #3 should finish after Operation #2
Operation #0 starts
Operation #1 starts
Operation #2 starts
Operation #3 starts
Operation #0 finished
Operation #3 finished
Operation #2 finished
Operation #1 finished
Run Code Online (Sandbox Code Playgroud)

这显然是不正确的.似乎OperationQueue只能确保操作以正确的顺序启动(而不是一个接一个地完成).虽然这可以使用DispatchSemaphore,但我想知道是否也可以使用OperationQueue.

mz2*_*mz2 8

操作依赖关系正在完成,而不是启动操作,因此系统的行为就像在那里记录的那样.问题是DispatchQueue(label: "operations").async- 你的performOperation方法在你内部异步调度序列之后立即退出print …; usleep …; success …,进入为每个performOperation调用创建的新调度队列.然后,在Grand Central Dispatch管理的工作线程池的不同线程上执行该序列的打印/睡眠/成功回调.

我认为你在这里可能会感到困惑的是,DispatchQueue(label: "operations")反复说明会让你获得相同的串行调度队列实例 - 事实并非如此,实际上你每次调用时都会创建一个新的串行队列.

顺便说一句,也没有理由创建或调度到你内部的串行调度队列performOperation,因为BlockOperation已经实现了这样,以便在支持OperationQueue的GCD调度队列上同时执行块(并发也可以限制).在我的例子中,我要做的是构造一个新的OperationQueue OperationQueue()(而不是使用OperationQueue.main哪个调度在主队列上工作),然后异步地将成功回调调度到主队列.

这个稍微修改过的例子向您展示了操作执行确实遵循了依赖关系(我没有实现上面的OperationQueue相关建议,它可以说是你提出的问题的旁边):

public func performOperation(_ number: Int, success: @escaping (Int) -> Void)->Void {
    print("Operation #\(number) starts")
    usleep(useconds_t(1000-(number*50))) // Block thread for some time
    success(number)
}

… 

let operationQueue = OperationQueue.main
for operationNumber in 0..<8 { // Create operations as an example
    let operation = BlockOperation(block: {
        self.performOperation(operationNumber) { number in
            print("Operation #\(number) finished")
        }
    })
    operation.name = "Operation #\(operationNumber)"

    if operationNumber > 0 {
        operation.addDependency(operationQueue.operations.last!)
        // Print dependencies
        print("\(operation.name!) should finish after \(operation.dependencies.first!.name!)")
    }
    operationQueue.addOperation(operation)
}
Run Code Online (Sandbox Code Playgroud)

这将输出......

Operation #1 should finish after Operation #0
Operation #2 should finish after Operation #1
Operation #3 should finish after Operation #2
Operation #4 should finish after Operation #3
Operation #5 should finish after Operation #4
Operation #6 should finish after Operation #5
Operation #7 should finish after Operation #6
Operation #0 starts
Operation #0 finished
Operation #1 starts
Operation #1 finished
Operation #2 starts
Operation #2 finished
Operation #3 starts
Operation #3 finished
Operation #4 starts
Operation #4 finished
Operation #5 starts
Operation #5 finished
Operation #6 starts
Operation #6 finished
Operation #7 starts
Operation #7 finished
Run Code Online (Sandbox Code Playgroud)