观察 URLSession 操作队列数?

Observe URLSession operation queue count?

我正在使用 URLSession 这样的设置:

public struct NetworkSession {
    
    public var session: URLSession
    public let sessionOperationQueue = OperationQueue()
    
    public init() {
        
        let sessionConfiguration = URLSessionConfiguration.default
        sessionConfiguration.timeoutIntervalForRequest = 20
        sessionOperationQueue.maxConcurrentOperationCount = OperationQueue.defaultMaxConcurrentOperationCount
        sessionOperationQueue.qualityOfService = .userInitiated
        session = URLSession(configuration: sessionConfiguration, delegate: nil, delegateQueue:sessionOperationQueue)
            
    }

    .....

}

我想观察在队列中找到的任务数。

我尝试使用 Combine:

    sessionOperationQueue.publisher(for: \.operationCount).sink { count in
        print("operations count: \(count)")
    }
    .store(in: &subscribers)

但这只会在初始化时打印 0,并且不会在请求开始和完成时更新。

如何监控队列中找到的任务数?

tl;dr

观察会话队列上的操作计数不会达到您想要的结果。

  • 在传统的 URLSession 代码中,队列用于各个委托方法,而不是包装整个 request-response。
  • 如果使用新的 async-await 再现,则根本不会使用操作队列(根据之前的观察,这是没有实际意义的)。

最重要的是,虽然 URLSession 有一种方法可以查询正在进行的未决请求,但它没有,AFAIK,对此有一个可观察的 属性(当然,除非你放弃完成处理程序并仅使用委托再现)。因此,如果您想动态跟踪待处理请求的计数,只需自己跟踪即可。异步自定义 Operation 子类模式似乎有点矫枉过正(但在 Operation 部分进行了概述)。最简单的方法是通过一种方法简单地路由我的所有网络请求,该方法在请求进入时递增计数器并在完成时递减计数器。


带有代码示例的长答案

您可以 use KVO to observe changes 队列的 operationCount(见下文),但这不会达到您想要的效果。这不是包装整个网络请求和响应的操作,而是针对单个会话委托和完成处理程序回调的单个操作。

例如,考虑:

class ViewController: UIViewController {

    lazy var session: URLSession = {
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = 20
        return URLSession(configuration: configuration, delegate: nil, delegateQueue: queue)
    }()

    let queue: OperationQueue = {
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 1
        queue.qualityOfService = .userInitiated
        return queue
    }()

    var observer: NSKeyValueObservation?

    override func viewDidLoad() {
        super.viewDidLoad()

        observer = queue.observe(\.operationCount, options: .new) { queue, change in
            print("Observer reported operationCount =", change.newValue!)
        }

        for i in 1000..<1010 {
            let url = URL(string: "https://httpbin.org/get?value=\(i)")!
            session.dataTask(with: url) { data, _, error in
                guard let data = data else {
                    print(error ?? URLError(.badServerResponse))
                    return
                }

                print("Request", i, "returned", data.count, "bytes")
            }.resume()
        }
    }
}

产生:

Observer reported operationCount = 1
Observer reported operationCount = 2
Observer reported operationCount = 3
Request 1000 returned 405 bytes
Observer reported operationCount = 4
Observer reported operationCount = 3
Request 1002 returned 405 bytes
Observer reported operationCount = 2
Request 1004 returned 405 bytes
Observer reported operationCount = 1
Request 1001 returned 405 bytes
Observer reported operationCount = 0
Observer reported operationCount = 1
Observer reported operationCount = 2
Request 1006 returned 405 bytes
Observer reported operationCount = 3
Observer reported operationCount = 2
Observer reported operationCount = 3
Request 1005 returned 405 bytes
Observer reported operationCount = 4
Observer reported operationCount = 3
Observer reported operationCount = 4
Request 1003 returned 405 bytes
Observer reported operationCount = 3
Request 1008 returned 405 bytes
Observer reported operationCount = 2
Request 1007 returned 405 bytes
Observer reported operationCount = 1
Request 1009 returned 405 bytes
Observer reported operationCount = 0

请注意,您永远不会看到它承认有十个请求待处理。 operationCount 正在报告代表队列中的内容,这不是您要查找的内容。

顺便说一下,在上面,委托队列是串行的(如 documentation 中所建议的)。它是一个允许并发网络请求的串行队列这一事实进一步证明没有包装整个请求的操作,而是针对单个委托回调。


有趣的是,如果您使用新的 async-await URLSession 方法,则根本不会使用操作队列。这是有道理的(假设它使用的是新的并发系统),但此时文档中没有注明。无论如何,以下将 不会 触发任何操作计数更改:

func startRequests() async throws {
    try await withThrowingTaskGroup(of: Void.self) { group in
        for i in 0..<4 {
            let url = URL(string: "https://httpbin.org/get?value=\(i)")!
            group.addTask {
                let (data, _) = try await self.session.data(from: url)
                print("Request", i, "returned", data.count, "bytes")
            }
        }

        try await group.waitForAll()
    }
}

但这没有实际意义,因为无论如何 URLSession 操作队列都没有达到您想要的效果。