Swift - Structured Concurrency - Notes about TaskGroup
Published: 2023-12-19 21:45:02

TaskGroup - Concurrent requests similar to DispatchGroup

- withTaskGroup - No throw variant. grp conforms to AsyncSequence, so return value could be collated by `for await` loop or `while let item = await grp.next()` or `await grp.reduce(initial, transform)`. `.waitForAll()` will discard the result though.
-- returnValue for each Task within the group should be the same representing `await withTaskGroup(String.self)` but overall group could return a different data type as well with collated Task results.

```
let something = await withTaskGroup(String.self) { grp -> String
grp.addTask {
...
...
return "abc" 
}
grp.addTask {
...
...
return "def" 
}
var collectedReturn = ""

//3 Ways to fetch
for await val in grp {
 // indv return from indv task and collate
 collectedReturn += val
}
return collectedReturn }
//OR
return await grp.reduce(""){$0 += $1} }
//OR
while let val = await grp.next() {
 collectedReturn +=val
}
return collectedReturn }
print("val is \(something)") // prints val is abcdef
```
- withThrowingTaskGroup - can throw Errors, grp conforms to AsyncSequence, so return value could be collated by `for try await` loop or `while let item = try await grp.next()` or `try await grp.reduce(initial, transform)`. `.waitForAll()` will discard the result though.
-- returnValue for each Task within the group should be the same representing `try await withTaskGroup([Model].self)` but overall group could return a different data type as well with collated Task results.

```
func loadStories() async {
        do {
            let allStories = try await withThrowingTaskGroup(of: [Model].self) { grp -> [Model] in
                
                for i in 1...5 {
                    grp.addTask {
                        let url = URL(string: "URL")!
                        let (data, _) = try await URLSession.shared.data(from: url)
                        _//if Task.isCancelled { return []}_
                        return try JSONDecoder().decode([Model].self, from: data)
                    }
                }
                _//grp.cancelAll()_
                //return try await grp.reduce(into: [Model]()) { $0 += $1 }
                var collectedItems = [Model]()
                while let item = try await grp.next() {
                    collectedItems.append(contentsOf: item)
                }
                return collectedItems
                
            }
            newsItems = allStories.sorted {
                $0.id < $1.id
            }
        } catch let error {
            if error as! CancelError == CancelError.Icancelled {
                print("I cancelled")
            }
            print("Error \(error.localizedDescription)")
        }
    }
```
- Task Cancellation: `.cancelAll()` Method on grp, wont cancel unless check for cancellation is cooperative but seems to work even otherwise, meaning check for cancellation such as `if Task.isCancelled` or `try Task.checkCancellation` calls within the grp's individual task as shown above in the comments.

- Heterogenous Return type for each task in Taskgroup is possible with wrapping up actual return type with enum so that each task can return same enum with different case having different associated value type. And still, taskGroup can ultimately return different data type as well.

My Notes as understood from HackingWithSwift Structured Concurrency series.