同步排队异步操作

时间:2019-02-07 19:09:10

标签: ios swift data-structures concurrency

我有一个显示在UITableView上的项目的列表/数组。每个项目内部都包含一个状态机,该状态机使用Futures和Promise机制执行一些异步任务,并且其状态显示在相应的UITableViewCell上。目前仅适用于一种型号。

但是,我需要按批处理顺序执行此操作。例如,阵列中可以有15个模型,但是在特定情况下,我只能启动3个模型,一旦模型完成或失败,我应该手动触发第4个模型来启动它的任务。注意:我无法启动所有15种模型操作,只能等待回调,因为它受到硬件的限制,在这种情况下会立即失败。

如果上面的内容不清楚,下面是两个示例: 我的问题陈述与iPhone的 App Store 应用程序中更新选项卡下的 全部更新 功能完全匹配。如果您有20个应用程序更新并点击“全部更新”按钮,则它将显示17个应用程序处于等待状态,并且随时仅在3个应用程序上运行更新下载。应用更新完成后,它将移至下一个更新。这是我的问题陈述的精确副本,但有一点点改动。

Twist:我的操作是通过蓝牙进行的与硬件相关的操作。假设您有20台可穿戴设备,要通过蓝牙写入一些数据进行配置。硬件限制是,您一次最多可以连接3-4个设备。因此,一旦设备/外围设备操作成功或失败,我应该尝试逐步连接4个设备,然后依次连接n个,直到全部完成。还有重试功能,可以将失败的功能排回队列。

我的问题是我应该如何构造以维护它并进行监视。我对并发有一个大致的了解,但是并没有做太多的工作。我目前的感觉是使用包装在Manager类中的Queue和计数器来监视状态。希望对如何解决这个问题有所帮助。另外,我不需要代码,只是数据结构的概念性解决方案。

2 个答案:

答案 0 :(得分:1)

使用OperationQueue,对于每种操作,您都有子类Operation。使用操作,您可以添加对其他操作的依赖关系以完成操作。因此,例如,如果您要等到第3个操作完成才开始第4、5和6个操作,则只需将第3个操作添加为它们的依赖项。

编辑:因此,可以将操作分组在一起,可以为其创建一个单独的类。我在下面添加了代码示例。 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()

答案 1 :(得分:1)

我认为您的反应很好。我使用ReactiveKit整理了一个简单的示例,以便您查看。反应套件非常简单,在这种情况下已绰绰有余。您也可以使用任何其他反应式库。希望对您有所帮助。

ReactiveKit:https://github.com/DeclarativeHub/ReactiveKit 债券:https://github.com/DeclarativeHub/Bond

安装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 }
    }
}