首页 文章

何时使用GCD队列,何时知道你需要它们?迅速

提问于
浏览
1

在阅读了Concurrent和Serial队列,同步和异步之后,我想我对如何创建队列及其执行顺序有所了解 . 我的问题是,在我看过的任何教程中,没有一个实际告诉你许多用例 . 例如:

我有一个网络管理器使用URLSessions并序列化json向我的api发送请求 . 将它包装在 .utility Queue或_2618027中是否有意义,或者我只是不将它包装在队列中 .

let task = LoginTask(username: username, password: password)

let networkQueue = DispatchQueue(label: "com.messenger.network", 
qos: DispatchQoS.userInitiated)

networkQueue.async {
    task.dataTask(in: dispatcher) { (user, httpCode, error) in
        self.presenter?.loginUserResponse(user: user, httpCode: httpCode, error: error)
    }
}

我的问题是:是否有任何指导我可以知道何时需要使用队列,因为我无法在任何地方找到这些信息 . 我意识到苹果提供了示例用法,但它非常模糊

1 回答

  • 1

    调度队列在很多用例中使用,因此很难枚举它们,但有两个非常常见的用例如下:

    • 您希望在当前线程以外的某个线程上运行一些昂贵且耗时的进程 . 当你在主线程上并且想要在后台线程上运行某些东西时,通常使用它 .

    一个很好的例子是图像处理,这是一个众所周知的计算(和内存)密集型过程 . 因此,您将为图像操作创建一个队列,然后您将每个图像处理任务分派到该队列 . 您可能还会在完成主队列后调度UI更新(因为所有UI更新必须在主线程上进行) . 常见的模式是:

    imageQueue.async {
        // manipulate the image here
    
        // when done, update the UI:
    
        DispatchQueue.main.async {
            // update the UI and/or model objects on the main thread
        }
    }
    
    • 您有一些共享资源(可能是一个简单的变量,它可能是某些其他共享资源(如文件或数据库)的交互),无论从哪个线程调用它,您都要进行同步 . 这通常是更广泛策略的一部分,这种策略是以线程安全的方式制作本质上不是线程安全的东西 .

    调度队列的优点在于它极大地简化了编写多线程代码,这是一种非常复杂的技术 .

    问题在于,启动网络请求的示例已经在后台线程上运行请求,并且 URLSession 为您管理所有这些,因此使用队列没什么 Value .


    为了完全公开,在上面讨论的基本调度队列之上和之外直接使用GCD(例如调度组或调度源)或间接(例如操作队列)的各种不同工具令人惊讶:

    • 调度组:有时您将启动一系列异步任务,并希望在完成所有任务后收到通知 . 您可以使用调度组(有关随机示例,请参阅https://stackoverflow.com/a/28101212/1271826) . 这使您无需跟踪所有这些任务自己完成的时间 .

    • Dispatch "apply"(现在称为 concurrentPerform ):有时当您运行一些大规模并行任务时,您希望使用尽可能多的线程 . 因此, concurrentPerform 允许您有效地并行执行 for 循环,Apple已针对您的特定设备的核心和CPU数量进行了优化,同时在任何时候都没有充斥着过多的并发任务,耗尽了有限数量的工作线程 . 有关并行运行 for 循环的示例,请参阅https://stackoverflow.com/a/39949292/1271826 .

    • 派遣来源:

    • 例如,如果您有一些后台任务正在做大量工作而您想要根据进度更新UI,有时这些UI更新可能比UI可以处理它们更快 . 因此,您可以使用调度源( DispatchSourceUserDataAdd )将后台进程与UI更新分离 . 有关示例,请参见前面提到的https://stackoverflow.com/a/39949292/1271826 .

    • 传统上, Timer 在主运行循环上运行 . 但有时你想在后台线程上运行它,但使用 Timer 这样做很复杂 . 但是您可以使用 DispatchSourceTimer (GCD计时器)在主队列以外的队列上运行计时器 . 有关如何创建和使用调度计时器的示例,请参阅https://stackoverflow.com/a/38164203/1271826 . 调度计时器也可用于避免使用基于 targetTimer 对象轻松引入的一些强引用循环 .

    • 障碍:有时在使用并发队列时,您希望大多数事情同时运行,但对于其他事情,要相对于队列中的其他所有内容进行串行运行 . 屏障是一种说“将此任务添加到队列中,但确保它不会运行的方法”与该队列上的任何其他内容同时发生 . “

    屏障的一个示例是读写器模式,其中从某些存储器资源读取可以与所有其他读取同时发生,但是任何写入都不能与队列中的任何其他内容同时发生 . 见https://stackoverflow.com/a/28784770/1271826https://stackoverflow.com/a/45628393/1271826 .

    • Dispatch信号量:有时您需要让在不同线程上运行的两个任务相互通信 . 您可以使用信号量为一个线程“等待”来自另一个线程的“信号” .

    信号量的一个常见应用是使固有异步任务以更同步的方式运行 .

    networkQueue.async {
        let semaphore = DispatchSemaphore(0)
        let task = session.dataTask(with: url) { data, _, error in
            // process the response
    
            // when done, signal that we're done
            semaphore.signal()
        }
        task.resume()
        semaphore.wait(timeout: .distantFuture)
    }
    

    这种方法的优点在于,在异步网络请求完成之前,调度的任务不会完成 . 因此,如果您需要发出一系列网络请求,但不能同时运行它们,信号量可以实现这一点 .

    但是,信号量应该谨慎使用,因为它们本身效率低下(通常会阻塞一个线程等待另一个线程) . 另外,请确保你从主线程中的信号量永远不会 wait (因为你在上面的例子中为什么我在等待 networkQueue ,而不是主队列 . 所有这些都被说过,通常比技术更好信号量,但它有时是有用的 .

    • 操作队列:操作队列 Build 在GCD调度队列之上,但提供了一些有趣的优点,包括:

    • 能够在自定义 Operation 子类中包装固有异步任务 . (这避免了我前面讨论过的信号量技术的缺点 . )调度队列通常在后台线程上运行固有同步任务时使用,但有时你想要管理一堆本身异步的任务 . 一个常见的例子是在 Operation 子类中包装异步网络请求 .

    • 能够轻松控制并发度 . 调度队列可以是串行队列或并发队列,但设计控制机制比较麻烦,例如说"run the queued tasks concurrent with respect to each other, but no more than four at any given time."操作队列使用 maxConcurrentOperationCount 可以更轻松 . (有关示例,请参阅https://stackoverflow.com/a/27022598/1271826 . )

    • 能够在各种任务之间 Build 依赖关系(例如,您可能有一个用于下载图像的队列和另一个用于操作图像的队列) . 使用操作队列,您可以有一个用于下载图像的操作,另一个用于处理图像,您可以使后者依赖于前者的完成 .


    还有很多其他GCD相关的应用程序和技术,但这些是我使用的一些频率 .

相关问题