Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python asyncio: stop the loop when one coroutine is done

I'm quite new in this python asyncio topic. I have a simple question: I have a task containing two coroutines to be run concurrently. First coroutine(my_coroutine) would just print something continuously until second_to_sleep is reached. The second coroutine(seq_coroutine) would call 4 other coroutines sequentially one after the other. My goal is to stop the loop at the end of seq_coroutine whenever it is completely finished. To be exact, I want my_coroutine be alive until seq_coroutine is finished. Can someone help me with that?

My code is like this:

import asyncio

async def my_coroutine(task,  seconds_to_sleep = 3):
    print("{task_name} started\n".format(task_name=task))
    for i in range(1, seconds_to_sleep):
        await asyncio.sleep(1)
        print("\n{task_name}: second {seconds}\n".format(task_name=task, seconds=i))

async def coroutine1():
    print("coroutine 1 started")
    await asyncio.sleep(1)
    print("coroutine 1 finished\n")


async def coroutine2():
    print("coroutine 2 started")
    await asyncio.sleep(1)
    print("coroutine 2 finished\n")


async def coroutine3():
    print("coroutine 3 started")
    await asyncio.sleep(1)
    print("coroutine 3 finished\n")


async def coroutine4():
    print("coroutine 4 started")
    await asyncio.sleep(1)
    print("coroutine 4 finished\n")


async def seq_coroutine():
    await coroutine1()
    await coroutine2()
    await coroutine3()
    await coroutine4()

def main():
    main_loop = asyncio.get_event_loop()
    task = [asyncio.ensure_future(my_coroutine("task1", 11)),
            asyncio.ensure_future(seq_coroutine())]
    try:
        print('loop is started\n')
        main_loop.run_until_complete(asyncio.gather(*task))
    finally:
        print('loop is closed')
        main_loop.close()


if __name__ == "__main__":
    main()

This is the output of this program:

loop is started

task1 started

coroutine 1 started

task1: second 1

coroutine 1 finished
coroutine 2 started

task1: second 2

coroutine 2 finished
coroutine 3 started

task1: second 3

coroutine 3 finished
coroutine 4 started

task1: second 4

coroutine 4 finished

task1: second 5
task1: second 6
task1: second 7
task1: second 8
task1: second 9
task1: second 10

loop is closed

I only want to have something like this:

loop is started

task1 started

coroutine 1 started

task1: second 1

coroutine 1 finished
coroutine 2 started

task1: second 2

coroutine 2 finished
coroutine 3 started

task1: second 3

coroutine 3 finished
coroutine 4 started

task1: second 4

coroutine 4 finished

loop is closed
like image 543
parsa Avatar asked Aug 29 '16 11:08

parsa


People also ask

How do I close Asyncio event loop?

stop() – the stop function stops a running loop. is_running() – this function checks if the event loop is currently running or not. is_closed() – this function checks if the event loop is closed or not. close() – the close function closes the event loop.

How do I stop an event loop?

uv_stop() can be used to stop an event loop. The earliest the loop will stop running is on the next iteration, possibly later.

How do I stop Asyncio tasks?

A Task is created and scheduled for its execution through the asyncio. create_task() function. Once scheduled, a Task can be requested for cancellation through task. cancel() method.

How do you stop a coroutine in Python?

To cancel a running Task use the cancel() method. Calling it will cause the Task to throw a CancelledError exception into the wrapped coroutine. If a coroutine is awaiting on a Future object during cancellation, the Future object will be cancelled. cancelled() can be used to check if the Task was cancelled.


2 Answers

I just found a suitable solution for my problem. I won't remove my post and I'll post my solution so that it may help others who face the same question. I used asyncio.wait(task, return_when=asyncio.FIRST_COMPLETED) and it will return the result whenever the first task is finished. This is the solution:

import asyncio
from asyncio.tasks import FIRST_COMPLETED
from concurrent.futures import CancelledError

async def my_coroutine(task,  seconds_to_sleep = 3):
    print("{task_name} started\n".format(task_name=task))
    for i in range(1, seconds_to_sleep):
        await asyncio.sleep(1)
        print("\n{task_name}: second {seconds}\n".format(task_name=task, seconds=i))

async def coroutine1():
    print("coroutine 1 started")
    await asyncio.sleep(1)
    print("coroutine 1 finished\n")


async def coroutine2():
    print("coroutine 2 started")
    await asyncio.sleep(1)
    print("coroutine 2 finished\n")


async def coroutine3():
    print("coroutine 3 started")
    await asyncio.sleep(1)
    print("coroutine 3 finished\n")


async def coroutine4():
    print("coroutine 4 started")
    await asyncio.sleep(1)
    print("coroutine 4 finished\n")


async def seq_coroutine(loop):
    await coroutine1()
    await coroutine2()
    await coroutine3()
    await coroutine4()

def main():
    main_loop = asyncio.get_event_loop()
    task = [asyncio.ensure_future(my_coroutine("task1", 11)),
            asyncio.ensure_future(seq_coroutine(main_loop))]
    try:
        print('loop is started\n')
        done, pending = main_loop.run_until_complete(asyncio.wait(task, return_when=asyncio.FIRST_COMPLETED))
        print("Completed tasks: {completed}\nPending tasks: {pending}".format(completed = done, pending = pending))

        #canceling the tasks
        for task in pending:
            print("Cancelling {task}: {task_cancel}".format(task=task, task_cancel=task.cancel()))

    except CancelledError as e:
        print("Error happened while canceling the task: {e}".format(e=e))
    finally:
        print('loop is closed')


if __name__ == "__main__":
    main()
like image 132
parsa Avatar answered Oct 22 '22 12:10

parsa


You can use a variable to signal to another coroutine. asyncio.Event is usually used:

import asyncio

import random


async def clock(name, event):
    print("* {} started".format(name))
    i = 0
    while not event.is_set():
        await asyncio.sleep(0.1)
        i += 1
        print("* {}: {}".format(name, i))
    print("* {} done".format(name))
    return i


async def coro(x):
    print("coro() started", x)
    await asyncio.sleep(random.uniform(0.2, 0.5))
    print("coro() finished", x)


async def seq_coroutine(name):
    event = asyncio.Event()
    clock_task = asyncio.ensure_future(clock(name, event))
    # await asyncio.sleep(0) # if you want to give a chance to clock() to start
    await coro(1)
    await coro(2)
    await coro(3)
    await coro(4)
    event.set()
    i = await clock_task
    print("Got:", i)


def main():
    main_loop = asyncio.get_event_loop()
    main_loop.run_until_complete(seq_coroutine("foo"))
    main_loop.close()


if __name__ == "__main__":
    main()

You can also use await event.wait() to block a piece of code until the event is set:

async def xxx(event):
    print("xxx started")
    await event.wait()
    print("xxx ended")
like image 45
Udi Avatar answered Oct 22 '22 12:10

Udi