在不影响资源的情况下处理Swift DispatchQueue

时间:2018-03-08 17:25:15

标签: swift asynchronous grand-central-dispatch

我有一个以60fps接收数据的Swift DispatchQueue。 然而,取决于电话或接收的数据量,这些数据的计算变得昂贵,以60fps处理。实际上,只处理其中一半或计算资源允许的数量是可以的。

let queue = DispatchQueue(label: "com.test.dataprocessing")

func processData(data: SomeData) {
    queue.async {
        // data processing 
    }
}

如果资源有限,DispatchQueue是否允许我删除一些数据?目前,它正在影响SceneKit的主要用户界面。或者,对于此类任务,是否有比DispatchQueue更好的东西?

1 个答案:

答案 0 :(得分:5)

有几种可能的方法:

  1. 简单的解决方案是跟踪您自己的Bool是否正在进行任务,当您有更多数据时,只有在没有正在运行的数据时才处理它: / p>

    private var inProgress = false
    private var syncQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".sync.progress")  // for reasons beyond the scope of this question, reader-writer with concurrent queue is not appropriate here
    
    func processData(data: SomeData) {
        let isAlreadyRunning = syncQueue.sync { () -> Bool in
            if self.inProgress { return true }
    
            self.inProgress = true
            return false
        }
    
        if isAlreadyRunning { return }
    
        processQueue.async {
            defer {
                self.syncQueue.async { self.inProgress = false }
            }
    
            // process `data`
        }
    }
    

    所有syncQueue内容都是为了确保我对inProgress属性具有线程安全访问权限。但是不要迷失在那些细节中;使用你想要的任何同步机制(例如锁或其他)。我们要确保的是,我们可以通过线程安全访问Bool状态标志。

    关注基本思想,即我们将跟踪Bool标志,以了解处理队列是否仍然处理前一组SomeData。如果它很忙,请立即返回,不要处理这些新数据。否则,请继续处理。

  2. 虽然上述方法在概念上很简单,但它不会提供出色的性能。例如,如果您的数据处理总是需要0.02秒(每秒50次)并且您的输入数据以每秒60次的速度进入,那么最终每秒处理30个数据。

    更复杂的方法是使用GCD用户数据源,即“当目标队列空闲时运行以下闭包”。这些调度用户数据源的优点在于它将它们合并在一起。这些数据源可用于将输入速度与处理速度分离。

    因此,您首先要创建一个数据源,仅指示数据进入时应该执行的操作:

    private var dataToProcess: SomeData?
    private lazy var source = DispatchSource.makeUserDataAddSource(queue: processQueue)
    
    func configure() {
        source.setEventHandler() { [unowned self] in
            guard let data = self.syncQueue.sync(execute: { self.dataToProcess }) else { return }
    
            // process `data`
        }
        source.resume()
    }
    

    因此,当需要处理数据时,我们会更新同步的dataToProcess属性,然后告诉数据源有什么要处理的内容:

    func processData(data: SomeData) {
        syncQueue.async { self.dataToProcess = data }
        source.add(data: 1)
    }
    

    再次,就像前面的例子一样,我们使用syncQueue来同步我们对多个线程的某些属性的访问。但是这一次我们正在同步dataToProcess而不是我们在第一个例子中使用的inProgress状态变量。但是这个想法是一样的,我们必须小心地将我们的交互与跨多个线程的属性同步。

    无论如何,使用这种模式与上述场景(输入以60 fps进行,而处理只能每秒处理50次),结果性能更接近理论最大值50 fps(我得到42到48 fps)取决于队列优先级),而不是30 fps。

  3. 后一个过程可以想象得到每秒处理更多的帧(或者你正在处理的任何帧),并且导致处理队列上的空闲时间减少。下图试图以图形方式说明两种替代方案的对比情况。在前一种方法中,您将丢失所有其他数据帧,而后一种方法只会在处理队列变为空闲之前进入两个独立的输入数据集并且它们合并为单个调用时丢失一帧数据到调度源。

    enter image description here