Python calling coroutine from normal function

Question:

So i have a script for a countdown which looks something like this:

import time, threading, asyncio
def countdown(n, m):
    print("timer start")
    time.sleep(n)
    print("timer stop")
    yield coro1

async def coro1():
    print("coroutine called")

async def coromain():
    print("first")
    t1 = threading.Thread(target=countdown, args=(5, 0))
    t1.start()
    print("second")

loop = asyncio.get_event_loop()
loop.run_until_complete(coromain())
loop.stop()

What i want it to do is simple:

Run coromain
Print "first"
Start thread t1, print "timer start" and have it wait for 5 seconds
In the mean time, print "second"
after 5 seconds, print "timer stop"
exit

However, when i run this code it outputs:

Run coromain
Print "first"
Print "second"
exit

I’m so confused as to why it does this. Can anyone explain what I’m doing wrong here?

Asked By: user7080613

||

Answers:

After some digging crafted this workaround. It might not be pretty, but it works:

import time, threading, asyncio
def countdown(n, m):
    print("timer start")
    time.sleep(n)
    print("timer stop")
    looptemp = asyncio.new_event_loop()
    asyncio.set_event_loop(looptemp)
    loop2 = asyncio.get_event_loop()
    loop2.run_until_complete(coro1())
    loop2.close()

async def coro1():
    print("coroutine called")

async def coromain():
    print("first")
    t1 = threading.Thread(target=countdown, args=(5, 0))
    t1.start()
    print("second")

loop = asyncio.get_event_loop()
loop.run_until_complete(coromain())
loop.stop()

It unfortunately doesn’t work for my specific usecase, but I thought it might be useful.

Answered By: user7080613

This depends on whether your question is a part of a bigger problem imposing additional constraints or not, but I do not see a reason to use threading. Instead, you can use two separate Tasks running in the same event loop, which is one of the main points of asynchronous programming:

import asyncio

async def countdown(n, m):  # <- coroutine function
    print("timer start")
    await asyncio.sleep(n)
    print("timer stop")
    await coro1()

async def coro1():
    print("coroutine called")

async def coromain():
    print("first")
    asyncio.ensure_future(countdown(5, 0))  # create a new Task
    print("second")

loop = asyncio.get_event_loop()
loop.run_until_complete(coromain())  # run coromain() from sync code
pending = asyncio.Task.all_tasks()  # get all pending tasks
loop.run_until_complete(asyncio.gather(*pending))  # wait for tasks to finish normally

Output:

first
second
timer start
(5 second wait)
timer stop
coroutine called

When using ensure_future, you effectively make a new “execution thread” (see fibers) inside the single OS’s thread.

Answered By: Norrius

I’m so confused as to why it does this. Can anyone explain what I’m doing wrong here?

There is already an accepted answer showing how to achieve what you want, but just to explain why your code produced the output it did:

The coroutine coromain starts the countdown as a thread, but does not wait for it to finish. Actually your coroutine, and therefore your program, exits before the thread could be exectued.

Answered By: maarten