ℹ️ Select 'Choose Exercise', or randomize 'Next Random Exercise' in selected language.

Choose Exercise:
Timer 00:00
WPM --
Score --
Acc --
Correct chars --

Concurrent Task Group Aggregation with Cancellation

Swift

Goal -- WPM

Ready
Exercise Algorithm Area
1import Foundation
2
3// Represents a network request result, either success with data or failure with an error.
4struct NetworkResult {
5let url: URL
6let data: Data?
7let error: Error?
8}
9
10// Fetches data from a single URL concurrently.
11func fetchData(from url: URL) async throws -> NetworkResult {
12do {
13let (data, _) = try await URLSession.shared.data(from: url)
14return NetworkResult(url: url, data: data, error: nil)
15} catch {
16// Log the error for debugging purposes.
17print("Error fetching data from \(url): \(error)")
18return NetworkResult(url: url, data: nil, error: error)
19}
20}
21
22// Aggregates results from multiple URLs using a concurrent task group.
23// If any task fails, all other tasks are cancelled.
24func aggregateConcurrentData(urls: [String]) async -> [NetworkResult] {
25var results: [NetworkResult] = []
26var urlObjects: [URL] = []
27
28// Pre-process URLs to handle potential invalid formats.
29for urlString in urls {
30guard let url = URL(string: urlString) else {
31print("Invalid URL format: \(urlString)")
32// Add a placeholder error result for invalid URLs.
33results.append(NetworkResult(url: URL(string: "invalid://\(urlString)")!, data: nil, error: URLError(.badURL)))
34continue
35}
36urlObjects.append(url)
37}
38
39// If all URLs were invalid, return early.
40if urlObjects.isEmpty {
41return results
42}
43
44// Use a task group to manage concurrent operations.
45await withTaskGroup(of: NetworkResult.self) {
46group -> Void in
47for url in urlObjects {
48// Add a new task to the group for each URL.
49group.addTask {
50// The task will execute fetchData and return its result.
51return await fetchData(from: url)
52}
53}
54
55// Iterate over the results as they become available.
56for await result in group {
57// If an error occurred in any task, cancel the entire group.
58if result.error != nil {
59print("Task failed, cancelling group.")
60group.cancelAll()
61// Add the failed result to our collection.
62results.append(result)
63// Break the loop as we are cancelling the group.
64break
65}
66// If successful, add the result.
67results.append(result)
68}
69}
70
71// Ensure all results are collected, even if cancellation occurred.
72// The loop above handles adding results as they complete or fail.
73// If cancellation happened, the loop breaks, and we return what we have.
74// If no cancellation, all results will be added.
75
76// Sort results by original URL order for consistency, if needed.
77// This is a simple sort; a more robust solution might use original index.
78results.sort { $0.url.absoluteString < $1.url.absoluteString }
79
80return results
81}
82
83// Example usage:
84// Task {
85// let urlsToFetch = [
86// "https://jsonplaceholder.typicode.com/posts/1",
87// "https://jsonplaceholder.typicode.com/posts/2",
88// "invalid-url",
89// "https://jsonplaceholder.typicode.com/posts/3"
90// ]
91// let fetchedResults = await aggregateConcurrentData(urls: urlsToFetch)
92// for result in fetchedResults {
93// if let error = result.error {
94// print("\(result.url): Error - \(error.localizedDescription)")
95// } else if let data = result.data {
96// print("\(result.url): Success - \(data.count) bytes")
97// }
98// }
99// }
Algorithm description viewbox

Concurrent Task Group Aggregation with Cancellation

Algorithm description:

This algorithm demonstrates how to concurrently fetch data from a list of URLs using Swift's `async/await` and `withTaskGroup`. It aggregates the results, ensuring that if any single network request fails, all other ongoing requests are immediately cancelled. This is crucial for efficient resource management and preventing unnecessary work in distributed systems or when dealing with potentially unreliable external services.

Algorithm explanation:

The `aggregateConcurrentData` function takes an array of URL strings and processes them concurrently. It first validates and converts the strings into `URL` objects, handling invalid formats by creating placeholder error results. Then, it uses `withTaskGroup` to launch a separate asynchronous task for each valid URL to fetch its data. The `fetchData` helper function performs the actual network request using `URLSession.shared.data(from:)`. The main function iterates over the results from the task group. If any task returns an error, `group.cancelAll()` is called to terminate all other running tasks, and the loop breaks. Otherwise, successful results are collected. This approach ensures that resources are not wasted on requests that are no longer needed after a failure. The time complexity is O(N) where N is the number of URLs, as each fetch is done in parallel, but the total time is dominated by the slowest request. Space complexity is O(N) to store the results.

Pseudocode:

function aggregateConcurrentData(urls):
  initialize results list
  initialize urlObjects list

  for each urlString in urls:
    try convert urlString to URL object
    if conversion fails:
      add error result to results
    else:
      add URL object to urlObjects

  if urlObjects is empty:
    return results

  start a task group for NetworkResult objects:
    for each url in urlObjects:
      add a task to the group that calls fetchData(from: url)

    for each result from the group:
      if result has an error:
        cancel all tasks in the group
        add result to results
        break loop
      else:
        add result to results

  sort results by URL string
  return results

function fetchData(from url):
  try fetch data from url using URLSession
  return NetworkResult with data and no error
  catch error:
    return NetworkResult with nil data and the error