'Best way to complete asyncio gather if one task is complete

I have the following code where function_1 and function_2 runs independently. However, I want the script to complete once function_2 is complete.

import asyncio
from datetime import datetime

class class_example():
    def __init__(self, bot):
        asyncio.run(self.main())

    async def main(self):
        f1=asyncio.create_task(self.function_1())
        f2=asyncio.create_task(self.function_2())
        a = await asyncio.gather(f1, f2)
        
    async def function_1(self):
        await asyncio.sleep(0.1)
        while True:
            print(f'{(datetime.now().strftime("%F - %H:%M:%S:%f"))} - function_1')
            await asyncio.sleep(1)

    async def function_2(self):
        i = 0
        while True:
            i += 1
            print(f'{(datetime.now().strftime("%F - %H:%M:%S:%f"))} - function_2')
            await asyncio.sleep(1)
            if i == 10:
                loop = asyncio.get_event_loop()
                loop.close()
                break

class manuel_run():
    def __init__(self):
        class_example(self)

if __name__ == '__main__':
    a = manuel_run()

I get this error message: RuntimeError: Cannot close a running event loop

I added a try/except for the error message and it works...but I believe there should be a better way to do this?

        try:
            a = await asyncio.gather(f1, f2)
        except RuntimeError as error:
            print(error)
            if 'Cannot close a running event loop' in str(error):
                print("FINISHED")
            else:
                print(error)


Solution 1:[1]

Unconditionally closing the event loop seems like a bad idea in the general case, since you may well have finalisation tasks that need to run before the program ends.

The easy and proper way out would be to cancel the other task. You can either do that by providing function_2 access to f1 by making it an attribute on your instance; or (better) using asyncio.wait (or maybe as_completed or other) in main:

done, pending = await asyncio.wait([f1, f2], return_when=asyncio. FIRST_COMPLETED)
a = await (list(done)[0])
list(pending)[0].cancel()

Note that you still might want to await the cancelled task.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1