Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios DispatchQueue.asyncAfter不';你不能像我想的那样工作吗?_Ios_Swift_Multithreading_Grand Central Dispatch - Fatal编程技术网

Ios DispatchQueue.asyncAfter不';你不能像我想的那样工作吗?

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

我正在开发一个应用程序,其中我试图调试的403禁止错误实际上是由每分钟可以向端点发出的请求量上限(愚蠢的我)引起的

很好,我决定将我的网络请求扔到一个DispatchQueue上(无论如何都是更好的并发设计),并使用asyncAfter(截止日期:execute:)函数在每个请求之间造成4秒的延迟

我的程序的设计是,列表中的每个项调用一个函数,在该函数中,一些工作(请求)被放入调度队列。见下文:

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秒,第