Solicitudes concurrentes de NSURLSession con Alamofire


Estoy experimentando un comportamiento extraño con mi aplicación de prueba. Tengo alrededor de 50 solicitudes GET simultáneas que envío al mismo servidor. El servidor es un servidor integrado en una pequeña pieza de hardware con recursos muy limitados. Para optimizar el rendimiento de cada solicitud, configuro una instancia de Alamofire.Manager de la siguiente manera:

let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPMaximumConnectionsPerHost = 2
configuration.timeoutIntervalForRequest = 30
let manager = Alamofire.Manager(configuration: configuration)

Cuando envío las solicitudes con manager.request(...) se envían en pares de 2 (como era de esperar, verificado con Charles HTTP Proxy). Lo extraño, sin embargo es decir, que todas las solicitudes que no terminaron dentro de los 30 segundos de la primera solicitud, se cancelan debido al tiempo de espera al mismo tiempo (incluso si aún no se han enviado). Aquí hay una ilustración que muestra el comportamiento:

ilustración de solicitud concurrente

¿Es este un comportamiento esperado y cómo puedo asegurarme de que las solicitudes no obtengan el tiempo de espera antes de que sean enviadas?

Muchas Gracias!

Author: Hannes, 2014-11-19

1 answers

Sí, este es el comportamiento esperado. Una solución es envolver sus solicitudes en una subclase personalizada y asíncrona NSOperation, y luego usar el maxConcurrentOperationCount de la cola de operaciones para controlar el número de solicitudes concurrentes en lugar del parámetro HTTPMaximumConnectionsPerHost.

El AFNetworking original hizo un trabajo maravilloso envolviendo las solicitudes en operaciones, lo que hizo que esto fuera trivial. Pero la implementación de AFNetworking NSURLSession nunca hizo esto, ni Alamofire.


Puede envolver fácilmente el Request en un NSOperation subclase. Por ejemplo:

class NetworkOperation: AsynchronousOperation {

    // define properties to hold everything that you'll supply when you instantiate
    // this object and will be used when the request finally starts
    //
    // in this example, I'll keep track of (a) URL; and (b) closure to call when request is done

    private let urlString: String
    private var networkOperationCompletionHandler: ((_ responseObject: Any?, _ error: Error?) -> Void)?

    // we'll also keep track of the resulting request operation in case we need to cancel it later

    weak var request: Alamofire.Request?

    // define init method that captures all of the properties to be used when issuing the request

    init(urlString: String, networkOperationCompletionHandler: ((_ responseObject: Any?, _ error: Error?) -> Void)? = nil) {
        self.urlString = urlString
        self.networkOperationCompletionHandler = networkOperationCompletionHandler
        super.init()
    }

    // when the operation actually starts, this is the method that will be called

    override func main() {
        request = Alamofire.request(urlString, method: .get, parameters: ["foo" : "bar"])
            .responseJSON { response in
                // do whatever you want here; personally, I'll just all the completion handler that was passed to me in `init`

                self.networkOperationCompletionHandler?(response.result.value, response.result.error)
                self.networkOperationCompletionHandler = nil

                // now that I'm done, complete this operation

                self.completeOperation()
        }
    }

    // we'll also support canceling the request, in case we need it

    override func cancel() {
        request?.cancel()
        super.cancel()
    }
}

Entonces, cuando quiera iniciar mis 50 solicitudes, haría algo como esto:{[17]]}

let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2

for i in 0 ..< 50 {
    let operation = NetworkOperation(urlString: "http://example.com/request.php?value=\(i)") { responseObject, error in
        guard let responseObject = responseObject else {
            // handle error here

            print("failed: \(error?.localizedDescription ?? "Unknown error")")
            return
        }

        // update UI to reflect the `responseObject` finished successfully

        print("responseObject=\(responseObject)")
    }
    queue.addOperation(operation)
}

De esa manera, esas solicitudes estarán limitadas por el maxConcurrentOperationCount, y no tenemos que preocuparnos de que ninguna de las solicitudes se extinga..

Este es un ejemplo AsynchronousOperation clase base, que se encarga de la KVN asociada con la subclase asíncrona/concurrente NSOperation:

//
//  AsynchronousOperation.swift
//
//  Created by Robert Ryan on 9/20/14.
//  Copyright (c) 2014 Robert Ryan. All rights reserved.
//

import Foundation

/// Asynchronous Operation base class
///
/// This class performs all of the necessary KVN of `isFinished` and
/// `isExecuting` for a concurrent `NSOperation` subclass. So, to developer
/// a concurrent NSOperation subclass, you instead subclass this class which:
///
/// - must override `main()` with the tasks that initiate the asynchronous task;
///
/// - must call `completeOperation()` function when the asynchronous task is done;
///
/// - optionally, periodically check `self.cancelled` status, performing any clean-up
///   necessary and then ensuring that `completeOperation()` is called; or
///   override `cancel` method, calling `super.cancel()` and then cleaning-up
///   and ensuring `completeOperation()` is called.

public class AsynchronousOperation : Operation {

    private let stateLock = NSLock()

    private var _executing: Bool = false
    override private(set) public var isExecuting: Bool {
        get {
            return stateLock.withCriticalScope { _executing }
        }
        set {
            willChangeValue(forKey: "isExecuting")
            stateLock.withCriticalScope { _executing = newValue }
            didChangeValue(forKey: "isExecuting")
        }
    }

    private var _finished: Bool = false
    override private(set) public var isFinished: Bool {
        get {
            return stateLock.withCriticalScope { _finished }
        }
        set {
            willChangeValue(forKey: "isFinished")
            stateLock.withCriticalScope { _finished = newValue }
            didChangeValue(forKey: "isFinished")
        }
    }

    /// Complete the operation
    ///
    /// This will result in the appropriate KVN of isFinished and isExecuting

    public func completeOperation() {
        if isExecuting {
            isExecuting = false
        }

        if !isFinished {
            isFinished = true
        }
    }

    override public func start() {
        if isCancelled {
            isFinished = true
            return
        }

        isExecuting = true

        main()
    }

    override public func main() {
        fatalError("subclasses must override `main`")
    }
}

/*
 Copyright (C) 2015 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information

 Abstract:
 An extension to `NSLock` to simplify executing critical code.

 From Advanced NSOperations sample code in WWDC 2015 https://developer.apple.com/videos/play/wwdc2015/226/
 From https://developer.apple.com/sample-code/wwdc/2015/downloads/Advanced-NSOperations.zip
 */

import Foundation

extension NSLock {

    /// Perform closure within lock.
    ///
    /// An extension to `NSLock` to simplify executing critical code.
    ///
    /// - parameter block: The closure to be performed.

    func withCriticalScope<T>( block: (Void) -> T) -> T {
        lock()
        let value = block()
        unlock()
        return value
    }
}

Hay otras posibles variaciones de este patrón, pero solo asegúrese de que usted (a) devuelve true para asynchronous; y (b) publica los necesarios isFinished y isExecuting KVN como se describe en la sección Configurando Operaciones para Ejecución Concurrente de la Guía de Programación de Concurrencia : Colas de operación.

 114
Author: Rob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-23 19:07:56