我试图跟踪当前的异步网络请求数,并仅在有一个或多个正在进行的请求时显示活动指示符。我使用的是调度组,但我认为我在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_leave
和callback
代码封装在单独的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
答案 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!")
}
}
}
}