在阅读了Concurrent和Serial队列,同步和异步之后,我想我已经知道了如何创建队列及其执行顺序。我的问题是在我见过的任何教程中,没有一个实际上告诉你许多用例。例如:
我有一个使用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)
}
}
我的问题是:当有需要使用队列时,是否有任何指导我可以知道,因为我无法在任何地方找到这些信息。我意识到苹果提供的示例用法如何非常模糊
答案 0 :(得分:1)
Dispatch队列在很多用例中使用,因此很难枚举它们,但是两个非常常见的用例如下:
您希望在当前线程以外的某个线程上运行一些昂贵且耗时的进程。通常,当您在主线程上运行并希望在后台线程上运行某些内容时,会使用此选项。
这方面的一个很好的例子是图像处理,这是一个众所周知的计算(和内存)密集型过程。因此,您需要为图像处理创建一个队列,然后您将每个图像处理任务分派到该队列。您可能还会在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(例如调度组或调度源)或间接(例如操作队列)的各种不同工具令人惊讶:
调度组:有时您会启动一系列异步任务,并希望在完成所有任务后收到通知。您可以使用调度组(有关随机示例,请参阅https://stackoverflow.com/a/28101212/1271826)。这使您无需跟踪所有这些任务的完成时间。
派遣"申请" (现在称为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。调度计时器还可用于避免使用基于target
的{{1}}对象轻松引入的一些强引用周期。
障碍:有时在使用并发队列时,您希望大多数事情同时运行,但其他事情要相对于队列中的其他所有内容进行串行运行。屏障是一种说法"将此任务添加到队列中的方法,但要确保它不会与该队列上的任何其他内容同时运行。"
屏障的一个示例是读写器模式,其中从某些内存资源读取可以与所有其他读取同时发生,但是任何写入都不能与队列中的任何其他内容同时发生。请参阅https://stackoverflow.com/a/28784770/1271826或https://stackoverflow.com/a/45628393/1271826。
Dispatch信号量:有时您需要让在不同线程上运行的两个任务相互通信。你可以使用一个线程的信号量来等待"等待"对于"信号"来自另一个人。
信号量的一个常见应用是使本质上异步的任务以更加同步的方式运行。
Timer
这种方法的优点在于,在异步网络请求完成之前,调度的任务不会完成。因此,如果您需要发出一系列网络请求,但不能同时运行它们,信号量可以实现这一点。
但是,信号量应该谨慎使用,因为它们本身效率低下(通常会阻塞一个线程等待另一个线程)。另外,请确保您从主线程中不会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
,而不是主队列。所有这些都已经说过,通常比信号量更好的技术,但它有时很有用。 操作队列:操作队列建立在GCD调度队列之上,但提供了一些有趣的优点,包括:
在自定义networkQueue
子类中包装固有异步任务的能力。 (这避免了我之前讨论过的信号量技术的缺点。)调度队列通常在后台线程上运行固有同步任务时使用,但有时你想要管理一堆本身异步的任务。一个常见的例子是在Operation
子类中包装异步网络请求。
轻松控制并发度的能力。调度队列可以是串行的或并发的,但是设计控制机制是很麻烦的,例如,说"相互之间并行运行排队的任务,但任何时候不超过四个给定时间。"使用Operation
操作队列可以更轻松。 (有关示例,请参阅https://stackoverflow.com/a/27022598/1271826。)
在各种任务之间建立依赖关系的能力(例如,您可能有一个用于下载图像的队列和另一个用于操作图像的队列)。使用操作队列,您可以使用一个操作来下载图像,另一个操作用于处理图像,您可以使后者依赖于前者的完成。
还有很多其他与GCD相关的应用程序和技术,但这些是我使用的一些频率。