5

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
2
  • 1
    Why not just run_until_complete(seq_coroutine)? Commented Aug 29, 2016 at 12:27
  • The idea of doing this concurrently is more than just sleeping and printing. "my_coroutine" is a process of listening to something else and should run in parallel with seq_coroutine, but to make it simpler to ask, i just make it shorter. Commented Aug 29, 2016 at 13:12

3 Answers 3

10

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()
Sign up to request clarification or add additional context in comments.

Comments

2

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")

Comments

2

Here's another way to do the same thing, which I think is cleaner in representing the dependence between jobs:

import asyncio

async def poll():
    i = 0
    while True:
        print("First", i)
        i += 1
        await asyncio.sleep(20)
        print("Second", i)
        i += 1
        await asyncio.sleep(20)
        
async def stop():
    poller = asyncio.ensure_future(poll())
    await asyncio.sleep(5)
    poller.cancel()

main_loop = asyncio.get_event_loop()
main_loop.run_until_complete(stop())
main_loop.close()

Basically, instead of breaking the entire event loop on a single job ending and then cancelling the job there, we just cancel the dependent job directly when the parent job finishes.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.