GCD - 跟踪总数或异步任务

GCD - Tracking total number or asynchronous tasks

我正在尝试跟踪当前的异步网络请求数,并仅在有一个或多个请求正在进行时显示 activity 指示器。我正在使用调度组,但我认为我在 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

dispatch_group_notify调度一个块对象在组为空时提交到队列。因此,在您的第二个示例中,您遇到了崩溃。由于从不同线程异步调用 print,日志中的消息出现乱序。这是真实情况:

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

只需用 dispatch_sync(dispatch_get_main_queue()) 包装所有 print 个调用,您将得到类似的结果。

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

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