即使在设置优先级和对操作的依赖之后,操作队列也没有按顺序执行 [英] Operation Queue not executing in order even after setting priority and dependency on Operations

查看:46
本文介绍了即使在设置优先级和对操作的依赖之后,操作队列也没有按顺序执行的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在进行三个 api 调用,并希望 API1 应该首先执行,一旦完成,API2 应该执行,然后是 API3.我为此使用了操作队列,并添加了对操作的依赖.我也尝试设置优先级,但没有按顺序获取 api 调用.帮我看看如何正确制作.

I am making three api calls and want that API1 should execute first, once completed API2 should execute followed by API3. I used operation queue for this with adding dependency over operations. I tried setting priority as well but not getting api calls in order. Help me out how to make it properly.

代码是这样的:

let op1 = Operation()
op1.completionBlock = {
    self.APICall(urlString: self.url1)
}
op1.queuePriority = .veryHigh

let op2 = Operation()
op2.completionBlock = {
    self.APICall(urlString: self.url2)
}
op2.queuePriority = .high

let op3 = Operation()
op3.completionBlock = {
    self.APICall(urlString: self.url3)
}

op3.queuePriority = .normal

op2.addDependency(op1)
op3.addDependency(op2)

queue.addOperations([op1, op2, op3], waitUntilFinished: false)

我将 API 调用方法放在 DispatchQueue.main.sync 中,如下所示:

I put the API Call Method in DispatchQueue.main.sync like this:

func APICall(urlString: String) {

    let headers: HTTPHeaders = [
        "Accept": "text/html"
    ]
    print(urlString)
    DispatchQueue.main.sync {

        Alamofire.request(urlString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: headers).responseJSON {
            response in
            // self.stopActivityIndicator()
            print(response.result.value)
            switch response.result {
            case .success:
                break
            case .failure(let error):
                break
            }
        }

    }
}

推荐答案

有几个问题:

  1. 如果您尝试管理操作之间的依赖项,则不能将操作的 completionBlock 用于依赖项所依赖的代码.直到操作完成后才会调用完成块(从而破坏了任何依赖项的目的).

  1. If you’re trying to manage dependencies between operations, you cannot use the operation’s completionBlock for the code that the dependencies rely upon. The completion block isn't called until after the operation is complete (and thus defeating the purpose of any dependencies).

因此以下内容不会按预期工作:

So the following will not work as intended:

let queue = OperationQueue()

let op1 = Operation()
op1.completionBlock = {
    print("starting op1")
    Thread.sleep(forTimeInterval: 1)
    print("finishing op1")
}

let op2 = Operation()
op2.completionBlock = {
    print("starting op2")
    Thread.sleep(forTimeInterval: 1)
    print("finishing op2")
}

op2.addDependency(op1)

queue.addOperations([op1, op2], waitUntilFinished: false)

但是如果你像这样定义操作,它就会起作用:

But if you define the operations like so, it will work:

let op1 = BlockOperation() {
    print("starting op1")
    Thread.sleep(forTimeInterval: 1)
    print("finishing op1")
}

let op2 = BlockOperation {
    print("starting op2")
    Thread.sleep(forTimeInterval: 1)
    print("finishing op2")
}

(但这只是因为我重新定义了同步操作.请参阅下面的第 3 点.)

(But this only works because I redefined operations that were synchronous. See point 3 below.)

值得注意的是,您通常从不直接使用 Operation.正如文档所说:

It’s worth noting generally you never use Operation directly. As the docs say:

代表与单个任务相关联的代码和数据的抽象类....

An abstract class that represents the code and data associated with a single task. ...

因为 Operation 类是一个抽象类,所以您不能直接使用它,而是使用它的子类或使用系统定义的子类之一(NSInvocationOperationBlockOperation) 来执行实际任务.

Because the Operation class is an abstract class, you do not use it directly but instead subclass or use one of the system-defined subclasses (NSInvocationOperation or BlockOperation) to perform the actual task.

因此使用上面的 BlockOperation 或子类化它,如下面的第 3 点所示.

Hence the use of BlockOperation, above, or subclassing it as shown below in point 3.

如果必须严格遵守顺序,则不应使用优先级来管理操作执行的顺序.正如 queuePriority 文档所说(强调补充):

One should not use priorities to manage the order that operations execute if the order must be strictly honored. As the queuePriority docs say (emphasis added):

此值用于影响操作出列和执行的顺序...

This value is used to influence the order in which operations are dequeued and executed...

您应该仅在需要时使用优先级值来对非依赖操作的相对优先级进行分类.不应该使用优先级值来实现不同操作对象之间的依赖管理.如果您需要在操作之间建立依赖关系,请改用 addDependency(_:) 方法.

You should use priority values only as needed to classify the relative priority of non-dependent operations. Priority values should not be used to implement dependency management among different operation objects. If you need to establish dependencies between operations, use the addDependency(_:) method instead.

因此,如果您将 100 个高优先级操作和 100 个默认优先级操作排队,则不能保证所有高优先级操作都会在低优先级操作开始运行之前启动.它会倾向于优先考虑它们,但并非严格如此.

So, if you queue 100 high priority operations and 100 default priority operations, you are not guaranteed that all of the high priority ones will start before the lower priority ones start running. It will tend to prioritize them, but not strictly so.

第一点没有实际意义,因为您正在调用异步方法.所以你不能使用简单的 OperationBlockOperation.如果您不希望在前一个网络请求完成之前启动后续网络请求,您需要将这些网络请求包装在自定义异步 Operation 子类中,并包含所有需要的特殊 KVO:>

The first point is moot, as you are calling asynchronous methods. So you can’t use simple Operation or BlockOperation. If you don’t want a subsequent network request to start until the prior one finishes, you’ll want to wrap these network request in custom asynchronous Operation subclass with all of the special KVO that entails:

class NetworkOperation: AsynchronousOperation {
    var request: DataRequest

    static var sessionManager: SessionManager = {
        let manager = Alamofire.SessionManager(configuration: .default)
        manager.startRequestsImmediately = false
        return manager
    }()

    init(urlString: String, parameters: [String: String]? = nil, completion: @escaping (Result<Any>) -> Void) {
        let headers: HTTPHeaders = [
            "Accept": "text/html"
        ]

        let string = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        let url = URL(string: string)!
        request = NetworkOperation.sessionManager.request(url, parameters: parameters, headers: headers)

        super.init()

        request.responseJSON { [weak self] response in
            completion(response.result)
            self?.finish()
        }
    }

    override func main() {
        request.resume()
    }

    override func cancel() {
        request.cancel()
    }
}

然后你可以这样做:

let queue = OperationQueue()

let op1 = NetworkOperation(urlString: ...) { result in
    ...
}

let op2 = NetworkOperation(urlString: ...) { result in
    ...
}

let op3 = NetworkOperation(urlString: ...) { result in
    ...
}

op2.addDependency(op1)
op3.addDependency(op2)

queue.addOperations([op1, op2, op3], waitUntilFinished: false)

并且因为使用了 AsynchronousOperation 子类(如下所示),所以在异步请求完成之前操作不会完成.

And because that’s using AsynchronousOperation subclass (shown below), the operations won’t complete until the asynchronous request is done.

/// Asynchronous operation base class
///
/// This is abstract to class performs all of the necessary KVN of `isFinished` and
/// `isExecuting` for a concurrent `Operation` subclass. You can subclass this and
/// implement asynchronous operations. All you must do is:
///
/// - override `main()` with the tasks that initiate the asynchronous task;
///
/// - call `completeOperation()` function when the asynchronous task is done;
///
/// - optionally, periodically check `self.cancelled` status, performing any clean-up
///   necessary and then ensuring that `finish()` is called; or
///   override `cancel` method, calling `super.cancel()` and then cleaning-up
///   and ensuring `finish()` is called.

public class AsynchronousOperation: Operation {

    /// State for this operation.

    @objc private enum OperationState: Int {
        case ready
        case executing
        case finished
    }

    /// Concurrent queue for synchronizing access to `state`.

    private let stateQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".rw.state", attributes: .concurrent)

    /// Private backing stored property for `state`.

    private var _state: OperationState = .ready

    /// The state of the operation

    @objc private dynamic var state: OperationState {
        get { stateQueue.sync { _state } }
        set { stateQueue.sync(flags: .barrier) { _state = newValue } }
    }

    // MARK: - Various `Operation` properties

    open         override var isReady:        Bool { return state == .ready && super.isReady }
    public final override var isAsynchronous: Bool { return true }
    public final override var isExecuting:    Bool { return state == .executing }
    public final override var isFinished:     Bool { return state == .finished }

    // KVN for dependent properties

    open override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
        if ["isReady", "isFinished", "isExecuting"].contains(key) {
            return [#keyPath(state)]
        }

        return super.keyPathsForValuesAffectingValue(forKey: key)
    }

    // Start

    public final override func start() {
        if isCancelled {
            state = .finished
            return
        }

        state = .executing

        main()
    }

    /// Subclasses must implement this to perform their work and they must not call `super`. The default implementation of this function throws an exception.

    open override func main() {
        fatalError("Subclasses must implement `main`.")
    }

    /// Call this function to finish an operation that is currently executing

    public final func finish() {
        if !isFinished { state = .finished }
    }
}

  • 作为非常小的观察,您的代码使用 JSON 参数指定了 GET 请求.这没有意义.GET 请求没有可以包含 JSON 的正文.GET 请求仅使用 URL 编码.此外,您没有传递任何参数.

  • As very minor observation, your code specified GET request with JSON parameters. That doesn’t make sense. GET requests have no body in which JSON could be included. GET requests only use URL encoding. Besides you’re not passing any parameters.

    这篇关于即使在设置优先级和对操作的依赖之后,操作队列也没有按顺序执行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆