串行队列/专用调度队列如何知道任务何时完成?

时间:2017-02-09 19:40:43

标签: ios swift grand-central-dispatch

(也许由How does a serial dispatch queue guarantee resource protection?回答,但我不明白怎么做)

问题

gcd如何知道异步任务(例如网络任务)何时完成?我应该为此目的使用dispatch_retaindispatch_release吗? 更新:我不能用ARC调用这些方法中的任何一种......有什么用?

详情

我正在与进行大量网络访问的第三方库进行交互。我已经通过一个小类创建了一个包装器,它基本上提供了我需要的第三方类的所有方法,但是将调用包装在dispatch_async(serialQueue) { () -> Void in中(其中serialQueue是我的包装类的成员)。 / p>

我正在努力确保每次对底层库的调用在下一次开始之前完成(不知何故,这些调用尚未在库中实现)。

3 个答案:

答案 0 :(得分:12)

串行调度队列的工作序列化是直接提交给队列的工作单元。一旦执行到达提交的闭包的末尾(或它返回),则可以执行队列上的下一个工作单元。

重要的是,闭包可能已经启动的任何其他异步任务可能仍在运行(或者甚至可能尚未开始运行),但不考虑它们。

例如,对于以下代码:

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秒任务是同步调度,所以串行队列被阻止,第二个任务在第一个任务完成之前没有开始。

在您的情况下,您正在包装的操作很可能会自己调度异步任务(因为这是网络操作的本质),因此串行调度队列本身是不够的。 / p>

您可以使用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.leavedg.wait()上还有一些采用超时参数的变体。

答案 1 :(得分:1)

如前所述,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
}

重要的是要注意一些事情。

  1. 请务必检查您的异步函数是否调用完成回调,有时候第三方库会忘记这一点,或者selfweak的情况,并且没有人在self时检查身体是否被评估1}}是nil。如果您没有检查它,那么您可能会挂起并且永远不会收到通知回调。
  2. 在致电group.enter()之前,请务必执行所有必需的group.async(group: group)group.notify来电。否则,您可以获得竞争条件,group.notify块可以在您完成任务之前触发。
  3.   

    坏样例

    let group = DispatchGroup()
    
    DispatchQueue.global().async {
       group.enter()
       syncTask1()
       group.leave()
    }
    
    group.notify(queue: .main) { 
        // Can run before syncTask1 completes - DON'T DO THIS
    }
    

答案 2 :(得分:0)

问题正文中问题的答案:

  

我正在尝试确保在下一次开始之前完成对底层库的每次调用

串行队列 保证任务按照您将其添加到队列的顺序进行。

我真的不明白标题中的问题:

  

如何在任务完成时串行队列... 知道