Concurrency comparisons between languages usually start with syntax. I'd start with what the calculation needs when something fails.
A small Python application fetches measurements and weights concurrently and combines them into one score. One fetch raises an exception while the other is still running.
First stop. Does one failed fetch make the whole calculation unusable? If so, asyncio.TaskGroup fits. When a member fails with anything other than CancelledError, the group cancels the remaining members and waits for its tasks before its exit finishes. One thing is easy to miss. Calling a coroutine function on its own schedules nothing, so the fetches have to become tasks in the group.
Second stop, a different decision. Both tasks return normally, but the weights are unsuitable. Collection has finished. Now let's inspect what came back. Reject the inputs, request replacements, or produce a partial result? TaskGroup has no opinion on that.
Third stop, caller cancellation. Fetch cleanup should let CancelledError propagate, since swallowing it can break the group's cancellation behavior.
For this application the answer is TaskGroup, because either failure ends the combined request and the scope has to wait for its members. Validating the returned data comes next, before any calculation. That gives a comparison with other languages something concrete to preserve.
