Swift 按顺序执行异步任务
Posted
技术标签:
【中文标题】Swift 按顺序执行异步任务【英文标题】:Swift execute asynchronous tasks in order 【发布时间】:2017-08-14 20:34:04 【问题描述】:我需要在我的应用上执行一些异步网络任务。假设我需要从服务器获取 3 个资源,分别称为 A
、B
和 C
。假设在获取B
或C
之前,我必须先完成获取资源A
。有时,我想先获取B
,其他时候先获取C
。
现在,我只有一个像这样的长链闭包:
func fetchA()
AFNetworking.get(completionHandler:
self.fetchB()
self.fetchC()
)
这暂时可行,但明显的限制是我已将执行顺序硬编码到 fetchA
的完成处理程序中。现在,假设我只想在fetchB
在完成处理程序中完成之后只使用fetchC
,我将不得不为fetchB
更改我的实现...
基本上,我想知道是否有一些神奇的方法可以做类似的事情:
let orderedAsync = [fetchA, fetchB, fetchC]
orderedAsync.executeInOrder()
其中fetchA
、fetchB
和fetchC
都是异步函数,但fetchB
在fetchA
完成之前不会执行等等。谢谢!
【问题讨论】:
不相关,但您可以考虑使用 Alamofire 而不是 AFNetworking。它出自同一作者之手,但却是一个 Swift 解决方案。但是你仍然需要做一些事情来处理异步任务,要么是 PromiseKit,要么是一些异步的Operation
子类来包装请求,或者类似的东西。
如果你确实留在了 AFNetworking,我为它做了一点 NSOperation
包装器:github.com/robertmryan/AFHTTPSessionOperation
【参考方案1】:
您可以将串行DispatchQueue
与DispatchGroup
混合使用,这将确保一次只运行一个执行块。
let serialQueue = DispatchQueue(label: "serialQueue")
let group = DispatchGroup()
group.enter()
serialQueue.async //call this whenever you need to add a new work item to your queue
fetchA
//in the completion handler call
group.leave()
serialQueue.async
group.wait()
group.enter()
fetchB
//in the completion handler call
group.leave()
serialQueue.async
group.wait()
group.enter()
fetchC
group.leave()
或者,如果您被允许使用第 3 方库,请使用 PromiseKit
,它比 GCD
提供的任何东西都更容易处理,尤其是链接异步方法。请参阅official GitHub 页面了解更多信息。
您可以将带有完成处理程序的异步方法包装在 Promise 中,并将它们链接在一起,如下所示:
Promise.wrap(fetchA(completion:$0)).then valueA->Promise<typeOfValueB> in
return Promise.wrap(fetchB(completion:$0)
.then valueB in
.catch error in
//handle error
此外,所有错误都会通过您的 Promise 传播。
【讨论】:
在解决方案中包含了一个 DispatchGroup,现在它可以肯定地工作了。还提供了 PromiseKit 作为替代方案。 承诺是必经之路! 是group.enter
等应该是dispatchGroup.enter
?
正如@dan 所说,如果包装操作 (fetchA/B/C) 本身是异步的,则第二个选项 (promises) 将不起作用。第一个(使用 GCD 组和“等待”)应该可以工作
@Sandeep 你是对的,并发队列也可以工作【参考方案2】:
您可以结合使用 dispatchGroup 和 dispatchSemaphore 来依次执行异步代码块。
当所有任务完成时,DispatchGroup 会保持进出通知。 值为 1 的 DispatchSemaphore 将确保只执行一个任务块示例 fetchA, fetchB, fetchC 是带有闭包的函数(完成处理程序)的代码
// Create DispatchQueue
private let dispatchQueue = DispatchQueue(label: "taskQueue", qos: .background)
//value 1 indicate only one task will be performed at once.
private let semaphore = DispatchSemaphore(value: 1)
func sync() -> Void
let group = DispatchGroup()
group.enter()
self.dispatchQueue.async
self.semaphore.wait()
fetchA() (modelResult) in
// success or failure handler
// semaphore signal to remove wait and execute next task
self.semaphore.signal()
group.leave()
group.enter()
self.dispatchQueue.async
self.semaphore.wait()
fetchB() (modelResult) in
// success or failure handler
// semaphore signal to remove wait and execute next task
self.semaphore.signal()
group.leave()
group.enter()
self.dispatchQueue.async
self.semaphore.wait()
fetchC() (modelResult) in
// success or failure handler
// semaphore signal to remove wait and execute next task
self.semaphore.signal()
group.leave()
group.notify(queue: .main)
// Perform any task once all the intermediate tasks (fetchA(), fetchB(), fetchC()) are completed.
// This block of code will be called once all the enter and leave statement counts are matched.
【讨论】:
【参考方案3】:不确定为什么其他答案会添加不必要的代码,您所描述的已经是串行队列的默认行为:
let fetchA = print("a starting"); sleep(1); print("a done")
let fetchB = print("b starting"); sleep(1); print("b done")
let fetchC = print("c starting"); sleep(1); print("c done")
let orderedAsync = [fetchA, fetchB, fetchC]
let queue = DispatchQueue(label: "fetchQueue")
for task in orderedAsync
queue.async(execute: task) //notice "async" here
print("all enqueued")
sleep(5)
"all enqueued" 将立即打印,每个任务将等待前一个任务完成后再开始。
仅供参考,如果您将 attributes: .concurrent
添加到您的 DispatchQueue
初始化中,则不能保证它们按顺序执行。但即便如此,当您希望事情按顺序执行时,您也可以使用.barrier
标志。
换句话说,这也可以满足您的要求:
let queue = DispatchQueue(label: "fetchQueue", attributes: .concurrent)
for task in orderedAsync
queue.async(flags: .barrier, execute: task)
【讨论】:
以上是关于Swift 按顺序执行异步任务的主要内容,如果未能解决你的问题,请参考以下文章