Ios DispatchQueue.asyncAfter不';你不能像我想的那样工作吗?
我正在开发一个应用程序,其中我试图调试的403禁止错误实际上是由每分钟可以向端点发出的请求量上限(愚蠢的我)引起的 很好,我决定将我的网络请求扔到一个DispatchQueue上(无论如何都是更好的并发设计),并使用asyncAfter(截止日期:execute:)函数在每个请求之间造成4秒的延迟 我的程序的设计是,列表中的每个项调用一个函数,在该函数中,一些工作(请求)被放入调度队列。见下文:Ios DispatchQueue.asyncAfter不';你不能像我想的那样工作吗?,ios,swift,multithreading,grand-central-dispatch,Ios,Swift,Multithreading,Grand Central Dispatch,我正在开发一个应用程序,其中我试图调试的403禁止错误实际上是由每分钟可以向端点发出的请求量上限(愚蠢的我)引起的 很好,我决定将我的网络请求扔到一个DispatchQueue上(无论如何都是更好的并发设计),并使用asyncAfter(截止日期:execute:)函数在每个请求之间造成4秒的延迟 我的程序的设计是,列表中的每个项调用一个函数,在该函数中,一些工作(请求)被放入调度队列。见下文: class ViewController let serialQueue = DispatchQu
class ViewController
let serialQueue = DispatchQueue(label: "networkRequests")
func myFirstFunc {
for item in items {
self.mySecondFunc(item: item, completionHandler: {(completionItem) in
// you shouldn't need this
})
}
}
func mySecondFunc(item: someType, completionHandler: @escaping (String?) -> Void) {
let task = session.dataTask(with: request, completionHandler: {(data, response, error) in
// stuff
completionHandler(changedItem)
})
self.serialQueue.asyncAfter(deadline: .now() + 4.0) {
task.resume()
print(Date())
}
}
}
}
我认为这将起作用的方式是,无论函数是否由不同的线程并发调用,asyncAfter(deadline:execute:)调用中包含的代码都将排队,直到最后一个闭包完成并再经过4秒,下一个闭包才会开始执行
这不起作用--打印(Date())在打印时间之间没有延迟
我通过在unix中使用sleep()函数解决了这个问题,但我很好奇在使用GCD的Swift中它是如何工作的
谢谢
编辑:特别是寻找一种正确的方法来实现这样的功能,即单个线程执行每个请求,从而阻止线程,直到前一个请求完成,其间有4秒的延迟 想想你的代码是做什么的。它通过一个循环运行,该循环接受数组中的每个项,并为每个项调用
asyncAfter()
。for循环的执行时间几乎为零,因此每个项都会获得相同的“从现在开始运行4秒”延迟。(好的,长列表中的最后一个可能比第一个延迟一微秒。)
如果希望每个请求在前一个请求启动后运行4秒,则需要增加请求之间的延迟:
class ViewController
let serialQueue = DispatchQueue(label: "networkRequests")
func myFirstFunc {
for (index, item) in items.enumerated {
self.mySecondFunc(item: item,
index: index,
completionHandler: {(completionItem) in
// you shouldn't need this
})
}
}
func mySecondFunc(
item: someType,
index: Int,
completionHandler: @escaping (String?) -> Void) {
let task = session.dataTask(with: request, completionHandler: {(data, response, error) in
// stuff
completionHandler(changedItem)
})
self.serialQueue.asyncAfter(deadline: .now() + 4.0 * index) {
task.resume()
print(Date())
}
}
}
}
请注意,您应该真正重构代码,在上一个任务完成之前不触发下一个请求,并跟踪发出每个请求的时间,计算在最后一分钟发出的请求数,一旦达到阈值,等待发出下一个请求,直到最后一分钟发出的请求总数降至最大值以下,因为最旧的请求“过期”
编辑:
在重新阅读了你的问题和评论之后,我对你想做的事情有了更好的了解。您试图利用这样一个事实:DispatchQueue(标签:)
initializer默认为您提供一个串行队列
问题在于URLSession是一个异步框架。启动下载任务时,它会立即返回。因此,您的串行任务队列都会很快完成,但下载任务会堆积在URLSession中,并根据其时间表运行
如果要使用串行队列进行下载,可以使用同步数据(contentsOf:)
初始值设定项同步读取数据:
let serialQueue = DispatchQueue(label: "networkRequests")
func readDataItems {
for item in items {
serialQueue.async {
let data = Data(contentsOf: item.url)
//Process this data item
sleep(4)
}
}
}
由于数据(contentsOf:)
函数是同步的,因此它将导致串行队列中的任务阻塞,直到任务完成。然后,在继续执行下一个任务之前,将任务休眠4秒钟
正如我在回答的第一部分所指出的,你应该真正跟踪每项任务完成的时间,只有在你没有超过允许的下载次数/分钟的情况下才开始下载
您还可以使用
操作队列
完成上述操作,具有更大的灵活性和控制能力。您的问题是任务.resume()
异步执行,因此mySecondFunc
退出,并在几毫秒后开始处理下一个请求。在第一个请求的初始延迟4秒后,这些请求最终被紧密地发送到一起
要解决此问题,请在第二个函数中添加延迟参数:
func myFirstFunc {
for (index, item) in items.enumerated() {
self.mySecondFunc(item: item, delay: index * 4) {completionItem in
// do stuffs
}
}
}
func mySecondFunc(item: SomeType, delay: TimeInterval = 0, completionHandler: @escaping (String?) -> Void) {
let task = session.dataTask(with: request) {data, response, error in
// stuff
completionHandler(changedItem)
}
self.serialQueue.asyncAfter(deadline: .now() + delay) {
task.resume()
print(Date())
}
}
这里列出的其他答案是有效的,但它们并没有像它们可能的那样强大。在固定的时间间隔调用
task.resume()
,并不保证在这些时间间隔发送请求。您受URLSession.shared
维护的内部队列的支配。以4秒的间隔添加任务并不意味着必须在该时间内发送任务。它也不能保证一个请求需要多长时间(想想服务差的移动网络)。至于数据(contentsOf:)
,它绝对不提供真正的特性或定制,例如错误处理
更可靠的解决方案是使用DispatchGroup
,只在前一个请求完成4秒后启动新请求
class ViewController
let serialQueue = DispatchQueue(label: "networkRequests")
let networkGroup = DispatchGroup()
func myFirstFunc {
for item in items {
self.mySecondFunc(item: item, completionHandler: {(completionItem) in
// you shouldn't need this
})
}
}
func mySecondFunc(item: someType, completionHandler: @escaping (String?) -> Void) {
let task = session.dataTask(with: request, completionHandler: {(data, response, error) in
// stuff
completionHandler(changedItem)
Thread.sleep(forTimeInterval: 4) // Wait for 4 seconds
networkGroup.leave() // Then leave this block so the next one can run
})
self.networkGroup.notify(queue: serialQueue) {
networkGroup.wait() // Wait for the previous block to finish
networkGroup.enter() // Enter a new block
task.resume()
print(Date())
}
}
}
这将保证每个后续请求在前一个请求完成后不早于4秒发送,并且不依赖外部因素(如
URLSession
的内部队列或网络稳定性)来维持适当的定时,在不牺牲URLSession
s的现代功能的情况下,这仍然存在严重的缺陷。延迟实际上需要在前一个请求实际完成后4秒。这个答案只是确保每个请求在前一个请求启动后4秒启动。由于网络问题,这仍然会导致所有请求同时发生。这回答了老年退休金计划的问题,但仍然不是问题的完整解决方案。@rmaddy,如果你有雄心壮志,你应该实施适当的解决方案,并将其作为更好的答案发布。“这比我有时间做的还多。”邓肯C:谢谢你的解释。这很有道理。我认为asyncAfter的工作方式是将闭包放在队列上,如果它是串行队列,则在最后一个队列完成并且又过了4秒之前,不会执行下一个队列。您将如何设计它,以便在同一线程上调用每个task.resume()+等待上一次调用完成,然后再等待4秒,第