Operation Queue not executing in order even after setting priority and dependency on Operations

血红的双手。 提交于 2021-02-07 19:42:28

问题


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.

Code is like this :

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)

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:


There are several issues:

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

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

    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. ...

    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.

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

  2. 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...

    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.

    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.

  3. 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()
        }
    }
    

    Then you can do:

    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)
    

    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 }
        }
    }
    
  4. 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.



来源:https://stackoverflow.com/questions/57246580/operation-queue-not-executing-in-order-even-after-setting-priority-and-dependenc

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!