52

Tôi đang gặp phải một số hành vi lạ với ứng dụng thử nghiệm của mình. Tôi đã có khoảng 50 yêu cầu GET đồng thời mà tôi gửi đến cùng một máy chủ. Máy chủ là một máy chủ được nhúng trên một phần cứng nhỏ với các tài nguyên rất hạn chế. Để tối ưu hóa hiệu suất cho từng yêu cầu duy nhất, tôi cấu hình một ví dụ của Alamofire.Manager như sau:NSURLSự yêu cầu đồng thời với Alamofire

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

Khi tôi gửi các yêu cầu với manager.request(...) họ nhận được cử theo cặp 2 (như mong đợi, kiểm tra với Charles HTTP Proxy). Điều kỳ lạ là, tất cả các yêu cầu không hoàn thành trong vòng 30 giây từ yêu cầu đầu tiên, bị hủy vì thời gian chờ tại cùng một thời điểm (ngay cả khi chúng chưa được gửi). Dưới đây là một minh họa trưng bày các hành vi:

concurrent request illustration

Đây có phải là một hành vi mong đợi và làm thế nào tôi có thể đảm bảo rằng các yêu cầu sẽ không nhận được thời gian chờ trước khi họ thậm chí còn gửi?

Cảm ơn rất nhiều!

+0

Có lẽ những gì bạn thực sự muốn đặt là ' timeoutIntervalForResource', không phải' timeoutIntervalForRequest'? – mattt

+0

Cảm ơn, nhưng tôi đã thử cả hai và điều tương tự vẫn xảy ra. – Hannes

Trả lời

104

Có, đây là hành vi mong đợi. Một giải pháp là bao bọc các yêu cầu của bạn trong lớp con NSOperation tùy chỉnh, không đồng bộ và sau đó sử dụng maxConcurrentOperationCount của hàng đợi hoạt động để kiểm soát số lượng yêu cầu đồng thời thay vì tham số HTTPMaximumConnectionsPerHost.

AFNetworking ban đầu đã thực hiện một công việc tuyệt vời bao gồm các yêu cầu trong hoạt động, điều này thực hiện tầm thường này. Nhưng việc thực hiện NSURLSession của AFNetworking không bao giờ làm điều này, cũng không phải Alamofire.


Bạn có thể dễ dàng quấn Request trong lớp con NSOperation. Ví dụ:

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

Sau đó, khi tôi muốn để bắt đầu 50 yêu cầu của tôi, tôi muốn làm một cái gì đó như thế này:

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

Bằng cách đó, những yêu cầu đó sẽ được hạn chế bởi maxConcurrentOperationCount, và chúng tôi không cần phải lo lắng về bất kỳ yêu cầu thời gian ra ..

Đây là một lớp cơ sở dụ AsynchronousOperation, mà sẽ chăm sóc của KVN gắn liền với không đồng bộ/đồng thời NSOperation lớp con:

// 
// 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 { 

    override public var isAsynchronous: Bool { return true } 

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

Có các biến thể khác có thể có của mẫu này, nhưng chỉ đảm bảo rằng bạn (a) trả lại true cho asynchronous; và (b) bạn đăng cần thiết isFinishedisExecuting KVN như đã nêu Định cấu hình hoạt động để thực hiện đồng thời phần của Concurrency Programming Guide: Operation Queues.

+1

Wow, cảm ơn Rob rất nhiều, không xảy ra thường xuyên để có được một câu trả lời tuyệt vời! Làm việc như một say mê. – Hannes

+0

wow câu trả lời tuyệt vời –

+0

Câu trả lời tuyệt vời. Thực sự hữu ích !!! Cảm ơn bạn!!! – Sendoa

Các vấn đề liên quan