简体   繁体   English

同步排队异步操作

[英]Synchronously queueing Asynchronous operations

I have a list/array of items which are being displayed on a UITableView. 我有一个显示在UITableView上的项目的列表/数组。 Each item contains a state machine inside it which does some asynchronous task using futures and promises mechanism and it's state being shown on respective UITableViewCell. 每个项目内部都包含一个状态机,该状态机使用Futures和Promise机制执行一些异步任务,并且其状态显示在相应的UITableViewCell上。 Currently for one model it works perfectly fine. 目前,对于一种模型,它可以完美工作。

However, I need to do this as a batching sequence. 但是,我需要按批处理顺序执行此操作。 For example there can be 15 models in an array however at a particular momement I'll be able to start only 3, once either models completes or fails I should manually trigger the 4th model to initiate it's task. 例如,阵列中可以有15个模型,但是在特定情况下,我只能启动3个模型,一旦模型完成或失败,我应该手动触发第4个模型来启动它的任务。 Note: I cannot initiate all of the 15 models operation and just wait for callback as it's limitation by hardware and immediately fail in that scenario. 注意:我无法启动所有15个模型的操作,只能等待回调,因为它受到硬件的限制,在这种情况下会立即失败。

To be specific if the above is not clear, below are two examples: My problem statement kind of exactly matches the Update all feature under Updates tab in App Store app of iPhone. 具体来说,如果上面的内容不清楚,下面是两个示例:我的问题陈述类型与iPhone的App Store应用程序中“ 更新”选项卡下的“全部更新”功能完全匹配。 If you have 20 app updates and tap on 'update all' button it shows 17 apps in waiting state and runs update downloads only on 3 apps at any moment. 如果您有20个应用程序更新并点击“全部更新”按钮,则它将显示17个应用程序处于等待状态,并且随时仅在3个应用程序上运行更新下载。 Once a app update completes, it moves to the next one. 应用更新完成后,它将移至下一个更新。 This is the exact replica of my problem statement, however with a small twist. 这是我的问题陈述的准确副本,但是有一点点改动。

Twist: My operations are hardware related operations over bluetooth. Twist:我的操作是通过蓝牙进行的与硬件相关的操作。 Think like you have 20 Wearable devices you want to configure with writing some data over bluetooth. 假设您有20台可穿戴设备,要通过蓝牙写入一些数据进行配置。 Hardware limitation is, you can connect to max 3-4 devices at a time. 硬件限制是,您一次最多可以连接3-4个设备。 Hence once a device/peripheral succeeds or fails with operation I should try to connect 4 one and so n so progressively until all are done. 因此,一旦设备/外围设备操作成功或失败,我应该尝试逐步连接4个设备,然后依次连接n个,直到全部完成。 There's retry function as well which queue back the failed one. 还有重试功能,可以将失败的功能排回队列。

My problem is how I should structure to maintain this and monitor. 我的问题是我应该如何构建结构以维护它并进行监视。 I have a general understanding of concurrency, however haven't worked much on it. 我对并发有一个大致的了解,但是并没有做太多的工作。 My current feeling is to use a Queue and counters wrapped in a Manager class to monitor the states. 我目前的感觉是使用包装在Manager类中的Queue和计数器来监视状态。 Would like some help on how to approach this. 希望对如何解决这个问题有所帮助。 Also I don't need code, just the conceptual solution to the data structure. 另外,我不需要代码,只需要数据结构的概念性解决方案。

Use OperationQueue and for each kind of operation you have subclass Operation . 使用OperationQueue ,对于每种操作,您都有子类Operation With operations you can add dependencies on other operations to finish. 使用操作,您可以添加对其他操作的依赖关系以完成操作。 So for example, if you wanted to wait until the 3rd operation is finished to start the 4th, 5th, and 6th operation you would just add the 3rd operation as their dependency. 因此,例如,如果您要等到第3个操作完成才开始第4、5和6个操作,则只需将第3个操作添加为它们的依赖项。

Edit: So to group operations together you can make a separate class for it. 编辑:因此要将操作分组在一起,可以为其创建单独的类。 I have added a code sample below. 我在下面添加了代码示例。 The add(dependency: OperationGroup) function tells the other group to start executing an operation as soon as an operation is finished executing in the original group. add(dependency: OperationGroup)函数告诉其他组在原始组中的操作完成执行后立即开始执行操作。

//Make a subclass for each kind of operation.
class BluetoothOperation: Operation
{
    let number: Int

    init(number: Int)
    {
        self.number = number
    }

    override func main() 
    {
        print("Executed bluetooth operation number \(number)")
    }
}

class OperationGroup
{
    var operationCounter: Int = 0
    var operations: [Operation]
    let operationQueue: OperationQueue = OperationQueue()

    init(operations: [Operation])
    {
        self.operations = operations
    }

    func executeAllOperations()
    {
        operationQueue.addOperations(operations, waitUntilFinished: true)
    }

    //This in essence is popping the "Stack" of operations you have.
    func pop() -> Operation?
    {
        guard operationCounter < operations.count else { return nil }

        let operation = operations[operationCounter]

        operationCounter += 1

        return operation
    }

    func add(dependency: OperationGroup)
    {
        dependency.operations.forEach(
        {
            $0.completionBlock =
            {
                if let op = self.pop()
                {
                    dependency.operationQueue.addOperation(op)
                }
            }
        })
    }
}


let firstOperationGroup = OperationGroup(operations: [BluetoothOperation(number: 1), BluetoothOperation(number: 2), BluetoothOperation(number: 3)])
let secondOperationGroup = OperationGroup(operations: [BluetoothOperation(number: 4), BluetoothOperation(number: 5), BluetoothOperation(number: 6)])

secondOperationGroup.add(dependency: firstOperationGroup)

firstOperationGroup.executeAllOperations()

I think yours is a good case for a little bit of reactive. 我认为您的反应性很好。 I put together a quick example using ReactiveKit so you can have a look. 我使用ReactiveKit整理了一个简单的示例,以便您查看。 Reactive kit is quite simple and is more than enough for this case. 反应套件非常简单,在这种情况下已绰绰有余。 You can use any other reactive library as well. 您也可以使用任何其他反应式库。 I hope it helps. 希望对您有所帮助。

ReactiveKit: https://github.com/DeclarativeHub/ReactiveKit Bond: https://github.com/DeclarativeHub/Bond ReactiveKit: https : //github.com/DeclarativeHub/ReactiveKit绑定: https : //github.com/DeclarativeHub/Bond

You can run the code below in a workspace after you install reactiveKit dependencies: 安装reactKit依赖项后,可以在工作区中运行以下代码:

import UIKit
import Bond
import ReactiveKit

class ViewController: UIViewController {

    var jobHandler : JobHandler!
    var jobs = [Job(name: "One", state: nil), Job(name: "Two", state: nil), Job(name: "Three", state: nil), Job(name: "Four", state: nil), Job(name: "Five", state: nil)]

    override func viewDidLoad() {
        super.viewDidLoad()
        self.jobHandler = JobHandler()
        self.run()
    }

    func run() {
        // Initialize jobs with queue state
        _ = self.jobs.map({$0.state.value = .queue})
        self.jobHandler.jobs.insert(contentsOf: jobs, at: 0)
        self.jobHandler.queueJobs(limit: 2) // Limit of how many jobs you can start with
    }
}

// Job state, I added a few states just as test cases, change as required
public enum State {
    case queue, running, completed, fail
}

class Job : Equatable {

    // Initialize state as a Reactive property
    var state = Property<State?>(nil)
    var name : String!

    init(name: String, state: State?) {
        self.state.value = state
        self.name = name
    }

    // This runs the current job
    typealias jobCompletion = (State) -> Void
    func runJob (completion: @escaping jobCompletion) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
            self.state.value = .completed
            completion(self.state.value ?? .fail)
            return
        }
        self.state.value = .running
        completion(.running)
    }

    // To find the index of current job
    static func == (lhs: Job, rhs: Job) -> Bool {
        return lhs.name == rhs.name
    }
}

class JobHandler {

    // The array of jobs in an observable form, so you can see event on the collection
    var jobs = MutableObservableArray<Job>([])
    // Completed jobs, you can add failed jobs as well so you can queue them again
    var completedJobs = [Job]()


    func queueJobs (limit: Int) {
        // Observe the events in the datasource
        _ = self.jobs.observeNext { (collection) in
            let jobsToRun = collection.collection.filter({$0.state.value == .queue})
            self.startJob(jobs: Array(jobsToRun.prefix(limit)))
        }.dispose()
    }

    func startJob (jobs: [Job?]) {
        // Starts a job thrown by the datasource event
        jobs.forEach { (job) in
            guard let job = job else { return }
            job.runJob { (state) in
                switch state {
                case .completed:
                    if !self.jobs.collection.isEmpty {
                        guard let index = self.jobs.collection.indexes(ofItemsEqualTo: job).first else { return }
                        print("Completed " + job.name)
                        self.jobs.remove(at: index)
                        self.completedJobs.append(job)
                        self.queueJobs(limit: 1)
                    }
                case .queue:
                    print("Queue")
                case .running:
                    print("Running " + job.name)
                case .fail:
                    print("Fail")
                }
            }
        }
    }
}

extension Array where Element: Equatable {
    func indexes(ofItemsEqualTo item: Element) -> [Int]  {
        return enumerated().compactMap { $0.element == item ? $0.offset : nil }
    }
} 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM