Reputation: 20138
I am using a URLSession
setup this way:
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)
}
.....
}
I would like to observe the count of tasks found in the queue.
I tried using Combine:
sessionOperationQueue.publisher(for: \.operationCount).sink { count in
print("operations count: \(count)")
}
.store(in: &subscribers)
But that only prints 0
at init and never updates as requests start and complete.
How can I monitor the number of tasks found in the queue?
Upvotes: 1
Views: 850
Reputation: 437402
tl;dr
Observing the operation count on the session’s queue will not achieve what you want.
URLSession
code, the queue is used for the individual delegate methods, not to wrap the whole request-response.async
-await
rendition, the operation queue is not used at all (which is moot, given the previous observation).Bottom line, while URLSession
has a method to inquire what pending requests are in progress, it does not, AFAIK, have an observable property for this (unless, of course, you abandon completion handlers and use only the delegate renditions). So, if you want to dynamically track of the count of pending requests, just keep track of this yourself. The asynchronous custom Operation
subclass pattern seems like overkill (but is outlined in the Operation
section of this answer). It would be easiest to simply route all my network requests through a method that increments a counter as requests come in and decrements it upon completion.
Long answer with code samples
You can use KVO to observe changes of the queue’s operationCount
(see below), but that is not going to achieve what you want. This is not an operation that wraps the whole network request and response, but rather individual operations for the individual session delegate and completion handler callbacks.
E.g., consider:
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()
}
}
}
That produces:
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
Note, you never see it acknowledge that there are ten requests pending. The operationCount
is reporting what’s on the delegate queue, which is not what you are looking for.
By the way, in the above, the delegate queue is serial (as advised in the documentation). The very fact that it is a serial queue allowing concurrent network requests is further evidence that there is not an operation wrapping the whole request, but rather is for the individual delegate callbacks.
As an interesting aside, if you use the new async
-await
URLSession
methods, the operation queue is not used at all. That makes sense (given that it is using the new concurrency system), but it is not noted in the documentation at this point. Anyway, the below will does not trigger any operation count changes:
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()
}
}
But this is moot, given that the URLSession
operation queue does not achieve what you want, regardless.
Upvotes: 4