I'm curious if any work has happened towards supporting a grouping concept like Yury has discussed with TaskGroup? I've been away from python for a while and I'm finding that the use of gather and wait is trickier than I remember it. My code tends to be inelegant when done as the docs recommend (using create_task first, etc). I think I benefit from a class based context manager primitive like TaskGroup.
Somewhat related, I recently watched Nathaniel's talk on the happy eyeballs algo (https://www.youtube.com/watch?v=oLkfnc_UMcE
) and I enjoyed the use of this very easy to understand algo as the test basis for concurrency designs. However when he described the problem my first instinct was that he would the write algo in a top-down style where the outer blocks handled scheduling and the inner tasks simply tried to do work or failed. It seemed to me the use of events objects, scheduling the next task from within an existing task (with conditions) and handling cleanup manually via cancel calls was less than perfect and I dare say not dissimilar to the reviled GOTO. Could this algo be written in even simpler terms? Namely, without calls the any sort of cancel, without the tasks themselves knowing anything about the outside scheduling or work set, and perhaps most importantly without the use of Event objects which are champions of the preemptive world IMO.
Please permit me to indulge in some hypothetical code I would like to be able to write someday to perhaps highlight my wishlist for asyncio...
async def might_fail_or_be_slow():
await asyncio.sleep(random.random() * 10)
if random.random() > 0.5:
raise Exception("job failed")
return "job worked"
async def happy_eyeballs(max_job_wait=0.200):
async with asyncio.group() as ag:
for i in range(10):
task = await ag.race(timeout=max_job_wait)
print("Nothing is ready yet, add another..")
return await task
print("Somebody failed, add another..")
raise Exception("nobody worked")
Some notes. This is obviously just pseudo code and I've not thought this through very carefully so it's obviously rough and incomplete. For one, I'm making the assumption here that the TaskGroup will be somewhat queue like. E.g. if you call `race()` the result is removed from the group; This might be silly or need to be more explicit, or maybe there could be a multitude of grouping classes for each use-case TaskQueue, TaskGroup, TaskRace, etc (spitballing). LIkewise it might be useful for the above TaskGroup class to have `remove_task(task)` , `wait(timeout=None) -> [tasks...]`, and perhaps even something like `results(timeout) -> [results from tasks...]` which is a shortcut method for `results = [await t for t in await group.wait()]`.