Concurrency By Turorials (4) - Groups & Semaphores

280 阅读3分钟

DispatchGroup

当您希望跟踪一组任务的完成情况时,可以使用DispatchGroup。

let group = DispatchGroup()
someQueue.async(group: group) { … your work … } someQueue.async(group: group) { … more work …. } someOtherQueue.async(group: group) { … other work … } 
group.notify(queue: DispatchQueue.main) { [weak self] in 
	self?.textLabel.text = “All jobs have completed” 
} 

正如上面的示例代码所示,DispatchGroup没有要求只能有一个queue。

可以使用单个DispatchGroup,但根据需要运行的任务的优先级,将任务提交到多个队列。

DispatchGroups提供一个notify(queue:)方法,您可以使用该方法在提交的每个任务完成后立即收到通知。

通知本身是异步的,所以在调用notify之后,只要先前提交的任务尚未完成,就可以向组提交更多任务。

Synchronous waiting

如果由于某种原因不能异步响应组的完成通知,则可以在DispatchGroup上使用wait方法。 这是一个同步方法,它将阻塞当前队列,直到所有作业都完成。 它使用一个可选参数,该参数指定等待任务完成所需的时间。如果没有指定,则有无限的等待时间,所以千万不要在主线程上调用:

let group = DispatchGroup()
someQueue.async(group: group) { … } someQueue.async(group: group) { … } someOtherQueue.async(group: group) { ... } 
if group.wait(timeout: .now() + 60) == .timedOut { 
print("The jobs didn’t finish in 60 seconds") } 

Wrapping asynchronous methods

因为如果在闭包内部调用异步方法,那么闭包将在内部异步方法完成之前完成。

所以必须以某种方式告诉任务,直到那些内部调用也完成了,它才会完成。

在这种情况下,您可以调用DispatchGroup上提供的enter和leave方法。

将它们看作是运行任务的简单计数。每次你进去,人数就增加1。当你离开的时候,人数减少了1:

queue.dispatch(group: group) { 
// count is 1 
group.enter() 
// count is 2 
someAsyncMethod {
defer { group.leave() } 
    // Perform your work here,
    // count goes back to 1 once complete
} } 

通过调用group.enter(),可以让DispatchGroup知道还有另一个代码块正在运行,这应该计入组的总体完成状态。

必须将其与对应的group.leave()调用配对,否则永远不会有完成的信号。

因为即使在出现错误的情况下也必须调用leave,所以通常需要使用一条defer语句,如上所示,无论如何退出闭包,group.leave()代码都会执行。

Semaphores

有时候确实需要控制有多少线程可以访问共享资源。

已经看到了限制对单个线程的访问的读/写模式,但是有时您可以允许一次使用更多的资源,同时仍然保持对总线程数的控制。

例如正在从网络下载数据,可能希望限制一次下载的次数。

您将使用一个DispatchQueue来卸载工作,并使用DispatchGroup以便您知道所有下载何时完成。但是只希望同时进行四次下载,因为知道要处理的数据非常大,而且资源非常多。

通过使用DispatchSemaphore,可以准确地处理该用例。在使用资源之前,只需调用wait方法,这是一个同步函数,线程将暂停执行,直到资源可用为止。如果还没有所有权,您将立即获得访问权。如果别人有,你就等着,直到他们发出结束的信号。

在创建Semaphores时,指定允许多少对资源的并发访问。如果希望同时启用4个网络下载,则传入4个。如果您试图为独占访问锁定资源,那么只需指定1。

let semaphore = DispatchSemaphore(value: 4)
for i in 110 { queue.async(group: group) { 
semaphore.wait()
defer { semaphore.signal() } 
print(“Downloading image \(i)”)
// Simulate a network wait
Thread.sleep(forTimeInterval: 3) print(“Downloaded image \(i)”) 
} } 

过程:立即看到发生了4次下载,然后,3秒钟后,又发生了4次下载。最后3秒钟后,最后2项完成。