与 Alamofire 并发请求

我的测试应用程序出现了一些奇怪的行为。我有大约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 Proxy 检查)。然而,奇怪的是,所有在第一个请求后30秒内没有完成的请求,都会因为同时超时而被取消(即使它们还没有被发送)。下面是一幅展示这种行为的插图:

concurrent request illustration

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

非常感谢!

19736 次浏览

是的,这是预料之中的行为。一种解决方案是将请求封装在自定义的异步 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)
}

That way, those requests will be constrained by the maxConcurrentOperationCount, and we don't have to worry about any of the requests timing out..

这是一个示例 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)返回 trueasynchronous; 和(b)你张贴必要的 isFinishedisExecuting KVN 的 并发编程指南: 操作队列为并发执行配置操作部分概述。