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

use*_*615 4 asynchronous grand-central-dispatch swift

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

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

func processData(data: SomeData) {
    queue.async {
        // data processing 
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Rob*_*Rob 7

有几种可能的方法:

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

    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`
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    所有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()
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

    func processData(data: SomeData) {
        syncQueue.async { self.dataToProcess = data }
        source.add(data: 1)
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

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

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

在此输入图像描述