【问题标题】:How does a serial queue/private dispatch queue know when a task is complete?串行队列/私有调度队列如何知道任务何时完成?
【发布时间】:2017-02-09 19:40:43
【问题描述】:

(也许How does a serial dispatch queue guarantee resource protection?回答了,但我不明白如何)

问题

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

详情

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

我试图确保对底层库的每次调用都在下一次调用开始之前完成(不知何故,这还没有在库中实现)。

【问题讨论】:

    标签: ios swift grand-central-dispatch


    【解决方案1】:

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

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

    例如,对于以下代码:

    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")
    }
    

    输出会是这样的:

    开始

    第一次完成

    开始

    第二次完成

    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 秒后

    第一次完成

    开始

    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.leavedg.wait() 也有一些变体,它们采用超时参数。

    【讨论】:

    • 这太棒了!问题:如果 DispatchGroup 是一个未解析的标识符,这是否意味着我在 Swift 2 上? 1? (我知道我不在 3 上)我是否应该像在(例如)commandshift.co.uk/blog/2014/03/19/… 中那样使用 dispatch_group_create、dispatch_group_enter 和 dispatch_group_leave?
    • 没错;我默认使用 Swift 3 :) 我可能应该清理我的答案,因为目前它是 2 和 3 的混合体。
    • 你认为 dispatch_group_wait 是必要的吗?就像我在答案的最后有两个块一样,dispatch_group_enter 和 dispatch_group_leave 调用不是已经实现了序列化吗? (然后输出与第二个块的输出相同?)
    • 不,dispatch_group_enter 不会阻止;您可以随意调用它多次。它只是不断增加 rhendispstch_group “计数”。 dispatch_group_leave 减少该计数,dispatch_group_wait 阻塞直到计数为 0。您还可以使用 dispatch_group_notify 提交要在计数变为 0 时执行的块。这用于当您想要执行某些代码时可能并行运行的异步任务数已完成。
    【解决方案2】:

    如前所述,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 是 @987654331 时是否对主体进行了评估@。如果您不检查它,那么您可能会挂起并且永远不会收到通知回调。
    2. 记得在调用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
    }
    

    【讨论】:

      【解决方案3】:

      问题正文中问题的答案:

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

      串行队列确实保证任务按照您将它们添加到队列中的顺序进行。


      虽然我不太明白标题中的问题:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-09
        • 1970-01-01
        • 2017-06-22
        • 2017-06-27
        相关资源
        最近更新 更多