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_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
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!")
}
}
}
}
我正在尝试跟踪当前的异步网络请求数,并仅在有一个或多个请求正在进行时显示 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_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
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!")
}
}
}
}