Swift 按顺序执行异步任务

Posted

技术标签:

【中文标题】Swift 按顺序执行异步任务【英文标题】:Swift execute asynchronous tasks in order 【发布时间】:2017-08-14 20:34:04 【问题描述】:

我需要在我的应用上执行一些异步网络任务。假设我需要从服务器获取 3 个资源,分别称为 ABC。假设在获取BC 之前,我必须先完成获取资源A。有时,我想先获取B,其他时候先获取C

现在,我只有一个像这样的长链闭包:

func fetchA() 
  AFNetworking.get(completionHandler: 
    self.fetchB()
    self.fetchC()
  )

这暂时可行,但明显的限制是我已将执行顺序硬编码到 fetchA 的完成处理程序中。现在,假设我只想在fetchB 在完成处理程序中完成之后只使用fetchC,我将不得不为fetchB 更改我的实现...

基本上,我想知道是否有一些神奇的方法可以做类似的事情:

let orderedAsync = [fetchA, fetchB, fetchC]
orderedAsync.executeInOrder()

其中fetchAfetchBfetchC 都是异步函数,但fetchBfetchA 完成之前不会执行等等。谢谢!

【问题讨论】:

不相关,但您可以考虑使用 Alamofire 而不是 AFNetworking。它出自同一作者之手,但却是一个 Swift 解决方案。但是你仍然需要做一些事情来处理异步任务,要么是 PromiseKit,要么是一些异步的 Operation 子类来包装请求,或者类似的东西。 如果你确实留在了 AFNetworking,我为它做了一点 NSOperation 包装器:github.com/robertmryan/AFHTTPSessionOperation 【参考方案1】:

您可以将串行DispatchQueueDispatchGroup 混合使用,这将确保一次只运行一个执行块。

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 按顺序执行异步任务的主要内容,如果未能解决你的问题,请参考以下文章

Js执行机制,同步任务异步任务

JavaScript 实现异步任务循环顺序执行

强制异步任务按顺序运行

详解如何构建Promise队列实现异步函数顺序执行

执行网络调用并继续 - 异步任务

nodejs所用的概念(同步,异步,事件驱动,事件循环等)通俗解释