GCD - 跟踪总数或异步任务

时间:2016-05-29 06:44:13

标签: ios swift grand-central-dispatch

我试图跟踪当前的异步网络请求数,并仅在有一个或多个正在进行的请求时显示活动指示符。我使用的是调度组,但我认为我在dispatch_group_notify块和closure块之间存在竞争条件,因为我偶尔会遇到dispatch_group_leave(taskGroup)的崩溃行:

fatal error: unexpectedly found nil while unwrapping an Optional value

我认为发生这种情况是因为当调度组中没有其他项目时,有时不会及时释放(设置为nil),之后会被稍后的请求使用(而不是新的小组正在创建)。然后,该组立即通知它是空的,调用了回调闭包,它被设置为nil,但仍然有一个额外的项目试图离开现在的nil组。

我认为解决方案在于确保dispatch_group_leave在触发最后dispatch_group_leave之后立即触发阻止,即在callback关闭之前。

我尝试将dispatch_group_leavecallback代码封装在单独的dispatch_sync闭包中,然后将它们添加到自定义串行队列中,但问题仍然存在于超过50%的所有执行中。

callback闭包调用包装在主队列的dispatch_async中(如下面的代码所示)会有所帮助,但问题仍然存在于大约10%的所有执行中。

这是我的代码(复制粘贴到Playground进行测试):

import UIKit
import XCPlayground

// Allow for asynchronous execution to take as long as it likes
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

// Background container view
let view = UIView(frame: CGRectMake(0, 0, 100, 100))
view.backgroundColor = UIColor.blackColor()
XCPlaygroundPage.currentPage.liveView = view

// Our activity indicator
let activityIndicator = UIActivityIndicatorView()
view.addSubview(activityIndicator)
activityIndicator.center = view.center

// Used to keep track of the number of current tasks
var taskGroup: dispatch_group_t!

// An async task that calls its callback after 2 to 5 seconds
func fireATask(callback: String -> Void) {

    if taskGroup == nil {
        print("Creating new dispatch group")
        taskGroup = dispatch_group_create()
        dispatch_group_enter(taskGroup)
        activityIndicator.startAnimating()
        dispatch_group_notify(taskGroup, dispatch_get_main_queue()) {
            activityIndicator.stopAnimating()
            taskGroup = nil
            print("All done!")
        }
    } else {
        print("Using existing dispatch group")
        dispatch_group_enter(taskGroup)
    }

    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {

        let delay = arc4random_uniform(1) + 2
        print("Task fired with [\(delay)] second delay.")
        let delayNanoseconds = Int64(UInt64(delay) * NSEC_PER_SEC)
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delayNanoseconds), dispatch_get_main_queue()) {

            dispatch_group_leave(taskGroup) // Sometimes crashing here because taskGroup is nil

            dispatch_async(dispatch_get_main_queue()) { // My attempt to make sure dispatch_group_notify is called before the callback
                callback("Task with [\(delay)] second delay finished!")
            }
        }
    }
}

我有时会得到很好的结果:

Creating new dispatch group
Using existing dispatch group
Task fired with [2] second delay.
Task fired with [2] second delay.
Task with [2] second delay finished!
All done!
Task with [2] second delay finished!
Creating new dispatch group
Task fired with [2] second delay.
All done!
Task with [2] second delay finished!

有时我遇到了崩溃:

Creating new dispatch group
Using existing dispatch group
Task fired with [2] second delay.
Task fired with [2] second delay.
Task with [2] second delay finished!
Using existing dispatch group
Task fired with [2] second delay.
All done!
Task with [2] second delay finished!
fatal error: unexpectedly found nil while unwrapping an Optional value

1 个答案:

答案 0 :(得分:2)

当组为空时,dispatch_group_notify会调度要提交到队列的块对象。因此,在你的第二个例子中,你得到了崩溃。由于异步调用来自不同线程的打印,日志中的消息显示为乱序。这是真实的情况:

Creating new dispatch group
Using existing dispatch group
Task fired with [2] second delay.
Task fired with [2] second delay.
Task with [2] second delay finished!
Task with [2] second delay finished!
All done!

Using existing dispatch group
Task fired with [2] second delay.
Task with [2] second delay finished!
fatal error: unexpectedly found nil while unwrapping an Optional value

只需使用print打包所有dispatch_sync(dispatch_get_main_queue())来电,您就可以获得类似的内容。

这就是我解决问题的方法:

// The execution queue
var tasksQueue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
var nTasks = 0

// An async task that calls its callback after 2 to 5 seconds
func fireATask(callback: String -> Void) {
    dispatch_async(tasksQueue) {

        dispatch_sync(dispatch_get_main_queue()) {
            nTasks += 1
            activityIndicator.startAnimating()
        }

        let delay = arc4random_uniform(1) + 2
        print("Task fired with [\(delay)] second delay.")
        let delayNanoseconds = Int64(UInt64(delay) * NSEC_PER_SEC)
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delayNanoseconds), dispatch_get_main_queue()) {
            callback("Task with [\(delay)] second delay finished!")
            nTasks -= 1
            if nTasks == 0 {
                activityIndicator.stopAnimating()
                print("All done!")
            }
        }
    }
}