Swift 何时使用GCD队列?何时知道需要它们?敏捷的

Swift 何时使用GCD队列?何时知道需要它们?敏捷的,swift,grand-central-dispatch,Swift,Grand Central Dispatch,在阅读了并发队列和串行队列、同步队列和异步队列之后,我想我已经了解了如何创建队列以及它们的执行顺序。我的问题是,在我看到的任何教程中,没有一个真正告诉您许多用例。例如: 我有一个网络管理器,它使用URLSessions和序列化json向我的api发送请求。将其包装在.utility队列或.userInitiated队列中有意义吗?或者我只是不将其包装在队列中 let task = LoginTask(username: username, password: password) let net

在阅读了并发队列和串行队列、同步队列和异步队列之后,我想我已经了解了如何创建队列以及它们的执行顺序。我的问题是,在我看到的任何教程中,没有一个真正告诉您许多用例。例如:

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

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)
    }
}

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

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

  • 您需要在当前线程以外的其他线程上运行一些昂贵和/或耗时的进程。当您在主线程上,并且希望在后台线程上运行某些内容时,通常会使用此选项

    一个很好的例子是图像处理,这是一个众所周知的计算(和内存)密集型过程。因此,您需要为图像处理创建一个队列,然后将每个图像处理任务分派到该队列。您还可以在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
    为您管理所有这些,因此使用队列来实现这一点没有什么价值


    为了充分披露,在上述基本调度队列之上和之外,有许多不同的工具直接使用GCD(例如调度组或调度源)或间接使用GCD(例如操作队列):

    • 调度组:有时,您将启动一系列异步任务,并希望在所有任务完成时收到通知。您可以使用调度组(请参见随机示例)。这使您无需跟踪所有这些任务何时由自己完成

    • Dispatch“apply”(现在称为
      concurrentPerform
      ):有时,当您运行一些大规模并行任务时,您希望使用尽可能多的线程。因此,
      concurrentPerform
      可以让您有效地并行执行
      for
      循环,苹果针对您的特定设备的内核和CPU数量进行了优化,同时不会在任何时候让它充斥太多并发任务,耗尽有限数量的工作线程。有关并行运行
      for
      循环的示例,请参见

    • 派遣来源:

      • 例如,如果您有一些正在做大量工作的后台任务,并且您希望随着进度更新UI,那么有时候这些UI更新的速度可能比UI处理它们的速度更快。因此,您可以使用分派源(DispatchSourceUserDataAdd)将后台进程与UI更新分离。参见上述示例

      • 传统上,计时器在主运行循环上运行。但有时您希望在后台线程上运行它,但使用
        计时器执行此操作非常复杂。但您可以使用
        DispatchSourceTimer
        (GCD计时器)在主队列以外的队列上运行计时器。有关如何创建和使用调度计时器的示例,请参见。调度计时器还可用于避免一些强引用循环,这些循环很容易通过基于
        目标
        计时器
        对象引入

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

      一个障碍的例子是读写器模式,其中从某个内存资源的读取可以与所有其他读取同时发生,但任何写入都不能与队列上的任何其他内容同时发生。看到或看到

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

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

      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)
      }
      
      这种方法的优点是,在异步网络请求完成之前,调度的任务不会完成。因此,如果您需要发出一系列网络请求,但不让它们同时运行,那么信号量可以完成这一任务

      不过,信号量应该少用,因为它们本身效率很低(通常会阻塞一个线程等待另一个线程)。另外,确保从不等待来自主线程的信号量(因为这样做会破坏异步任务的目的)。这就是为什么在上面的示例中,我等待的是
      网络队列
      ,而不是主队列。所有这些都已经说过了