【问题标题】:NSURLSession concurrent requests with AlamofireNSURLSession 与 Alamofire 的并发请求
【发布时间】:2015-01-17 06:27:57
【问题描述】:

我的测试应用出现了一些奇怪的行为。我有大约 50 个同时发送到同一服务器的 GET 请求。服务器是资源非常有限的小型硬件上的嵌入式服务器。为了优化每一个请求的性能,我配置了一个Alamofire.Manager的实例如下:

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

当我使用 manager.request(...) 发送请求时,它们会成对地分派 2 个(正如预期的那样,通过 Charles HTTP 代理进行检查)。奇怪的是,所有在第一个请求后 30 秒内没有完成的请求,都会因为超时而同时被取消(即使它们还没有被发送)。这是展示行为的插图:

这是预期的行为吗?如何确保请求在发送之前不会超时?

非常感谢!

【问题讨论】:

  • 也许你真正想要设置的是`timeoutIntervalForResource, not timeoutIntervalForRequest`?
  • 谢谢,但我都试过了,同样的事情一直在发生。
  • 您的方法在 Alamofire 4 中不再有效,请更新它
  • 你用什么程序来制作这张图?
  • 为漂亮的图表点赞,让问题变得超级清晰 - 我一直在到处寻找答案,但很难用语言解释发生了什么

标签: swift concurrency nsurlsession alamofire nsurlsessionconfiguration


【解决方案1】:

是的,这是预期的行为。一种解决方案是将您的请求包装在自定义的异步NSOperation 子类中,然后使用操作队列的maxConcurrentOperationCount 来控制并发请求的数量,而不是使用HTTPMaximumConnectionsPerHost 参数。

最初的 AFNetworking 将请求封装在操作中做得非常好,这使得这变得微不足道。但是 AFNetworking 的 NSURLSession 实现从未这样做过,Alamofire 也没有。


您可以轻松地将Request 包装在NSOperation 子类中。例如:

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()
    }
}

然后,当我想发起 50 个请求时,我会这样做:

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)
}

这样,这些请求将受到maxConcurrentOperationCount 的约束,我们不必担心任何请求超时..

这是一个示例AsynchronousOperation 基类,它负责与异步/并发NSOperation 子类关联的KVN:

//
//  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`")
    }
}

/*
 Abstract:
 An extension to `NSLocking` to simplify executing critical code.

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

import Foundation

extension NSLocking {

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

    func withCriticalScope<T>(block: () throws -> T) rethrows -> T {
        lock()
        defer { unlock() }
        return try block()
    }
}

此模式还有其他可能的变体,但只需确保 (a) 为 asynchronous 返回 true; (b) 您发布必要的isFinishedisExecuting KVN,如Concurrency Programming Guide: Operation Queues配置并发执行操作部分所述。

【讨论】:

  • 哇,非常感谢 Rob,获得如此好的答案并不常见!像魅力一样工作。
  • 所有用户在 Stack Overflow 上的贡献都是通过cc by-sa 3.0attribution required 贡献的。请参阅此网页底部页脚中的链接。最重要的是,作者保留对 Stack Overflow 贡献的版权,但我们也授予永久许可,以将这些特定贡献用于任何目的,包括商业目的,唯一要求是 (a) 需要署名和 (b)你会分享。简而言之,是的,它可以免费使用。
  • @JAHelia - 不,我没有,因为我的AsynchronousOperation 在执行后将闭包设置为nil,从而解决了任何强引用循环。
  • @famfamfam - 在此异步操作模式中,您创建新操作,在此操作完成后执行您需要的操作,并使其依赖于各个网络请求的所有单独操作。
  • @famfamfam "我看到你设置了maxConcurrentOperationCount = 2,但你确实调用了 50 次请求......" 这就是重点:OP 想要排队 50 个请求,但从来没有超过两个同时同时运行。 maxConcurrentOperationCount 只是指示在任何给定时间可以运行多少个。 (你不希望同时运行太多,因为(a)URLSession 一次只能运行这么多,所以你有可能让后面的请求超时;和(b)内存影响。)上面实现了一个受控排队许多请求时的并发程度。
猜你喜欢
  • 1970-01-01
  • 2020-09-30
  • 1970-01-01
  • 2013-10-06
  • 1970-01-01
  • 2017-01-23
  • 1970-01-01
  • 2017-05-11
  • 2017-10-31
相关资源
最近更新 更多