Как ограничить количество сетевых звонков в течение l oop? - PullRequest
2 голосов
/ 18 января 2020

У меня есть несколько объектов на сервере для каждого пользователя, и я должен получить их все. Во-первых, я получаю количество объектов с сервера, чтобы вычислить количество сетевых вызовов, которые я должен сделать в пакетах по 30. Допустим, если есть 2000 объектов, то количество вызовов, которые я должен сделать, составляет 2000/30 = 67. Я устанавливаю смещение = 0 и предел = 29 для первого вызова, а затем увеличиваю как смещение, так и предел на 30, и выполняю второй вызов и так далее. Проблема заключается в том, что если я отправляю более 8 вызовов одновременно в течение l oop, то вызовы начинают сбой, и даже если я повторяю, он продолжает сбой. Я использую 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 Ответ

2 голосов
/ 19 января 2020

Семафоры

Это похоже на хороший вариант использования семафоров.

enter image description here Кредиты: https://unsplash.com/photos/5F04PN6oWeM

Позвольте мне показать вам, как это сделать с Playground, чтобы вы могли запустить это решение локально и затем импортировать его в свой проект.

Playground

Прежде всего, создайте новая пустая страница Playground и эти 3 строки, чтобы импортировать необходимые библиотеки и включить параллелизм.

import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

Как только вы решите перенести это решение в свой проект, вам понадобится только строка import Foundation .

Contants

Теперь давайте определим следующие 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 , тогда сетевой вызов не выполнен , вместо этого он включен ждать , пока не закончится один из предыдущих вызовов.

Как только сетевые вызовы заканчиваются, мы вызываем

// 2
semaphore.signal

Таким образом, значение семафора увеличивается на 1 и разрешает выполнение другого ожидающего вызова.

Test

Теперь мы вызываем call

fetchAll { results in
    print(results)
}

Не будет более 8 одновременных вызовов fetchData и как только все вызовы будут завершены, будет напечатан результат

["?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?"]

Заключение

Надеюсь, это поможет, если вы хотите получить более подробную информацию, посмотрите этот ответ, где я говорю семафоры { ссылка }

...