I am new to concurrency in Swift and I am trying to understand how to control the maximum number of concurrent tasks in a task group.

The following is a snippet I use to make concurrent requests. The specified endpoint returns a single integer after waiting (sleeping) for 1 second.

Expectation

Code takes ~1 second (excluding compilation time) total for requesting and retrieving 20 responses.

Actual

Code takes ~4 seconds and in the server, I can see it is being requested in batches of four. 6 + 6 + 6 + 2. This to me says that 6 is the max limit.

I want to understand how is this limit defined, and how I can increase / decrease this as I see fit.

Code

import Foundation

func makeRequests() async -> [Int] {
    let url = URL(string: "http://localhost:8000")!
    return await withTaskGroup(of: Int.self) {
        g in 
        (0...20).forEach {
            i in g.addTask {
                try! await JSONDecoder().decode(Int.self, from: URLSession.shared.data(from: url).0)
            }
        }
        return await g.reduce(into: []) { $0.append($1) }
    }
}

print(await makeRequests())

My Effort

I did a bit of googling but I kept seeing references to DispatchQueue which from what I understand is supposed to be replaced by async / await? I also saw some (possibly roundabout) suggestions that involve fetching using next after reaching a certain limit, I guess this is fine if I want to rate limit it, but I am not sure how I can modify this approach to go over a certain limit (which is 6 in my case).

Apologies for the verboseness but I hope it explains my ask :slight_smile:

PS: I have confirmed my server can handle at least 20 concurrent requests. I used Python to make concurrent requests and I was able to make all of them in ~1 second. Just specifying this so that any questions regarding the server is not raised.

Most likely this has something to do with the number of cpu cores on the machine where you are sending the requests from.

1 Like

Thank you, it seems I have 6 "performance" cores. If it's not too much to ask, can you direct me towards an approach that lets me override this limit? Should I still look into DispatchQueues or has newer alternatives emerged since?

I am fine with wanting to opt into a different limit under the assumption that it may lead to "thread explosions".

I would just stick with the Swift task group, and let Swift's concurrency engine do its work. I don't believe you would gain anything if you were to be able to override the limit.

I am not an expert, so I would like to hear what @John_McCall would kindly say about this.

2 Likes

This is the URLSession simultaneous request limit, not anything to do with Swift concurrency. You can try adjusting the limit by passing a custom URLSessionConfiguration but the system has an overall maximum you can’t exceed. If you want to test maximum concurrency parallelism, I suggest you do only local work.

3 Likes

I changed httpMaximumConnectionsPerHost in a custom config and I was able to get the output I expected. I also tried your other suggestion re: local work and I can confirm it works the way I expected it.

Thank you for both your suggestions.

You can also try using HTTP 2 or 3, which allow multiple simultaneous requests on a single connection.

2 Likes