串行队列/专用调度队列如何知道任务何时完成?
(或许通过串行调度队列如何保证资源保护来回答?但我不明白如何)
题
gcd如何知道异步任务(例如网络任务)何时完成? 我应该为此目的使用dispatch_retain
和dispatch_release
吗? 更新:我不能用ARC调用这些方法中的任何一个……怎么办?
细节
我正在与进行大量网络访问的第三方库进行交互。 我通过一个小类创建了一个包装器,基本上提供了我需要的第三方类的所有方法,但是将调用包装在dispatch_async(serialQueue) { () -> Void in
(其中serialQueue
是我的包装类的成员) 。
我试图确保每个对底层库的调用在下一次开始之前完成(某种程度上,它尚未在库中实现)。
串行调度队列上的工作序列化是在直接提交给队列的工作单元上。 一旦执行到达提交的闭包的末尾(或它返回),则可以执行队列上的下一个工作单元。
重要的是,闭包可能已经启动的任何其他异步任务可能仍在运行(或者甚至可能尚未开始运行),但不考虑它们。
例如,对于以下代码:
dispatch_async(serialQueue) { print("Start") dispatch_async(backgroundQueue) { functionThatTakes10Seconds() print("10 seconds later") } print("Done 1st") } dispatch_async(serialQueue) { print("Start") dispatch_async(backgroundQueue) { functionThatTakes10Seconds() print("10 seconds later") } print("Done 2nd") }
输出将是这样的:
开始
完成第1
开始
完成第二
10秒后
10秒后
请注意,在调度第二个串行任务之前,前10秒任务尚未完成。 现在,比较:
dispatch_async(serialQueue) { print("Start") dispatch_sync(backgroundQueue) { functionThatTakes10Seconds() print("10 seconds later") } print("Done 1st") } dispatch_async(serialQueue) { print("Start") dispatch_sync(backgroundQueue) { functionThatTakes10Seconds() print("10 seconds later") } print("Done 2nd") }
输出将是这样的:
开始
10秒后
完成第1
开始
10秒后
完成第二
请注意,这次是因为同步调度了10秒的任务,所以串行队列被阻塞,第二个任务在第一个任务完成之前没有启动。
在您的情况下,您正在包装的操作很可能会自己调度异步任务(因为这是网络操作的本质),因此串行调度队列本身是不够的。
您可以使用DispatchGroup
来阻止串行调度队列。
dispatch_async(serialQueue) { let dg = dispatch_group_create() dispatch_group_enter(dg) print("Start") dispatch_async(backgroundQueue) { functionThatTakes10Seconds() print("10 seconds later") dispatch_group_leave(dg) } dispatch_group_wait(dg) print("Done") }
这将输出
开始
10秒后
完成
dg.wait()
阻塞串行队列,直到dg.leave
调用的数量与dg.enter
调用的数量相匹配。 如果您使用此技术,则需要注意确保包装操作的所有可能完成路径都调用dg.leave
。 dg.wait()
上还有一些变量,它们采用超时参数。
如前所述, DispatchGroup
是一个非常好的机制。
您可以将它用于同步任务:
let group = DispatchGroup() DispatchQueue.global().async(group: group) { syncTask() } group.notify(queue: .main) { // done }
最好使用notify
不是wait
,因为wait
会阻塞当前线程,因此在非主线程上是安全的。
您还可以使用它来执行异步任务:
let group = DispatchGroup() group.enter() asyncTask { group.leave() } group.notify(queue: .main) { // done }
或者您甚至可以执行任何同步的任意数量的并行任务:
let group = DispatchGroup() group.enter() asyncTask1 { group.leave() } group.enter() //other way of doing a task with synchronous API DispatchQueue.global().async { syncTask1() group.leave() } group.enter() asyncTask2 { group.leave() } DispatchQueue.global().async(group: group) { syncTask2() } group.notify(queue: .main) { // runs when all tasks are done }
重要的是要注意一些事情。
- 总是检查你的异步函数是否调用完成回调,有时候第三方库会忘记这一点,或者当你
self
weak
情况下,没有人费心去检查当self
是nil
是否对身体进行了评估。 如果您不检查它,那么您可能会挂起并且永远不会收到通知回调。 - 在调用
group.notify
之前,请记住执行所有必需的group.enter()
和group.async(group: group)
调用。 否则,您可以获得竞争条件,并且在实际完成任务之前,group.notify
块可以触发。
不好的例子
let group = DispatchGroup() DispatchQueue.global().async { group.enter() syncTask1() group.leave() } group.notify(queue: .main) { // Can run before syncTask1 completes - DON'T DO THIS }
你问题机构中问题的答案:
我试图确保每个对底层库的调用在下一个开始之前完成
串行队列确保按照您将任务添加到队列的顺序进行任务。
我不太明白标题中的问题:
串行队列如何 … 知道任务何时完成?