如何限制for循环中的网络调用次数?
Posted
技术标签:
【中文标题】如何限制for循环中的网络调用次数?【英文标题】:How to limit the number of network calls in a for loop? 【发布时间】:2020-01-18 15:38:16 【问题描述】:我在服务器上为每个用户提供了一些对象,我必须全部获取它们。首先,我从服务器获取对象计数,以计算我必须分批进行的网络调用次数 30。假设有 2000 个对象,那么我必须进行的调用次数为 2000/30 = 67。我为第一次调用设置偏移量 = 0 和限制 = 29,然后将偏移量和限制都增加 30 并进行第二次调用,依此类推。问题是,如果我在 for 循环中同时发送超过 8 个调用,那么调用开始失败,即使我重试它仍然失败。我正在使用 Alamofire 向服务器发送发布请求。我注意到,如果我并行发送 8 个请求,一切似乎都很好。如何确保并行进行 8 个调用,当所有 8 个调用都完成后,再向服务器发送另外 8 个调用。
这是一段代码:
let count = Double(totalTransactionsCount)
//The maximum amount of transactions to fetch in one call.
//let maxLimit = 30
let calls = Int(ceil(count/Double(maxLimit)))
for call in 1...calls
print("Calling call \(call) with offset:\(offset) to limit:\(limit)")
callFetchTransactionWith(offset, limit)
offset += maxLimit
limit += maxLimit
fileprivate func callFetchTransactionWith(_ offset: Int, _ limit: Int, _ callCountPercentage: Double, _ calls: Int)
TransactionsModel.reportTransactions(offset: offset, limit: limit ,success: (transactions) in
ACTIVITYCALL += 1
self.currentSyncingProgress = self.currentSyncingProgress + CGFloat(callCountPercentage)
Utility.logger(log: "\(self.currentSyncingProgress)", message: "Activity Increment Progress")
if ACTIVITYCALL == calls
TransactionsModel.assignSequenceNumbers()
self.didCompleteProgressAndSync(duration: 2.0)
return
else
self.updateProgressBar(value: self.currentSyncingProgress)
, failure: (response, statusCode) in
print(response,statusCode)
self.callFetchTransactionWith(offset, limit, callCountPercentage, calls)
)
static func reportTransactions(offset:Int,limit:Int,
success:@escaping ( _ model: [TransactionsModel] ) -> Void,
failure:@escaping APIClient.FailureHandler)
let params:Parameters = [
"offset":offset,
"limit":limit
]
let headers:[String:String] = [
"X-Auth-Token":Singleton.shared.token
]
if !Connectivity.isConnectedToInternet
Constants.UIWindow?.showErrorHud(text: AppString.internetUnreachable)
return
APIClient.shared().requestPost(endpoint: Route.reportTransactions, params: params, headers: headers, success: (response) in
guard
let data = response as? [String:Any],
let transactions = data["transactions"] as? [[String : Any]]
else return
let transactionModels = Mapper<TransactionsModel>().mapArray(JSONArray: transactions)
TransactionsModel.save(transactions: transactionModels)
success(transactionModels)
) (response, status) in
print(response,status)
failure(response,status)
【问题讨论】:
【参考方案1】:信号量
这看起来是一个很好的信号量用例。
致谢:https://unsplash.com/photos/5F04PN6oWeM
让我向您展示如何使用 Playground 执行此操作,以便您可以在本地运行此解决方案,然后将其导入到您的项目中。
游乐场
首先创建一个新的空 Playground 页面和这 3 行,以便导入所需的库并启用并发。
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
一旦您决定将此解决方案移动到您的项目中,您只需要
import Foundation
行。
目录
现在让我们定义以下 3 个常量。
并发调用 这是您要执行的并发调用数。
let concurrentCalls = 8
信号量 该信号量将允许执行不超过 8 个线程。当第 9 个线程请求访问时,它会处于等待状态,直到正在运行的 8 个线程之一完成。
let semaphore = DispatchSemaphore(value: concurrentCalls)
后台队列 我们将使用此队列异步调度所有调用。
let backgroundQueue = DispatchQueue(label: "Background queue")
fetchData(完成:
此函数模拟您的远程 API 调用。它只是等待 3 秒,然后通过代表结果的“?”字符串调用完成。
func fetchData(completion: @escaping (String) -> Void)
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0)
completion("?")
fetchAll(完成:)
现在我们处于解决方案的核心。
func fetchAll(completion: @escaping ([String]) -> Void)
// 1
let storageQueue = DispatchQueue(label: "Serial queue")
var results = [String]()
let totalNumberOrCalls = 20
for i in 0..<totalNumberOrCalls
backgroundQueue.async
semaphore.wait()
fetchData result in
storageQueue.async
results.append(result)
if i == totalNumberOrCalls - 1
completion(results)
// 2
semaphore.signal()
它是如何工作的?
我们有一个值设置为 8 的信号量。
每次我们想要执行网络调用时,我们都会询问信号量是否可以开始调用
// 1
semaphore.wait()
如果信号量的值大于 0,则它允许我们的远程调用并减少它的值。
否则,如果信号量保持为 0,则网络调用未执行,而是将其置于 wait 直到之前的调用之一结束。
一旦网络调用结束,我们就会调用
// 2
semaphore.signal
这样,信号量值增加 1,并允许执行另一个等待调用。
测试
现在我们调用调用
fetchAll results in
print(results)
对fetchData
的并发调用不会超过 8 个,一旦所有调用完成,就会打印结果
["?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?"]
结论
希望这会有所帮助,如果您想了解更多详细信息,请查看我谈论信号量的这个答案https://***.com/a/51816661/1761687
【讨论】:
if i == totalNumberOrCalls - 1
。给定的结果可能是错误的,因为与其他任务相比,它可能是一个非常短的任务,它将在其他任务完成之前执行(除非信号量的值为 1)。为了防止您必须对已执行的任务进行计数器,当counter == totalNumberOfCalls - 1
时,我们才会到达最后一个以上是关于如何限制for循环中的网络调用次数?的主要内容,如果未能解决你的问题,请参考以下文章