4

I am been trying to run two functions simultaneously but one never seems to work unless I stop the other. The first function sends an email every 30 seconds while the second prints a simple statement every 5 seconds. In combination, per every 6 "Hello Worlds" outputs one email should be sent.

However, I never get an email unless the printing is changed to stop early, such as ending after 10 seconds. What can I do to have both running concurrently without stopping?

async def timer():
    end = time.time() + 30
    while True:
        if time.time() >= end:
            sendmail(name, filepath + "\\" + name, receiver)
            end = time.time() + 30

async def runs():
    while True:
        print("Hello World")
        time.sleep(5)


loop = asyncio.get_event_loop()
loop.create_task(runs())
loop.create_task(timer())
loop.run_forever()
2
  • 1
    async provides cooperative concurrency. timer is not written cooperatively - it never suspends, e.g. via asyncio.sleep(0). Commented Jun 20, 2020 at 15:18
  • 2
    Ideally you'd also want an awaitable version of sendmail. Commented Jun 20, 2020 at 16:24

2 Answers 2

8

Python's async coroutines are meant for cooperative concurrency. That means that coroutines must actively allow others to run. For simple cases, use await asyncio.sleep to pause the current coroutine and run others.

async def timer():
    while True:
        await asyncio.sleep(30)  # instead of `if time.time() >= end:…`
        sendmail(name, filepath + "\\" + name, receiver)

async def runs():
    while True:
        print("Hello World")
        await asyncio.sleep(5)  # instead of `time.sleep(5)`

async def main():
    await asyncio.gather(timer(), runs())

asyncio.run(main())

Notably, do not use time.sleep – this blocks the entire thread, meaning the current coroutine as well as the event loop and all other coroutines, until the sleep is over.
Similarly, avoid any synchronous code with significant runtime – asyncio cannot switch to other coroutines while synchronous code runs. If needed, use an asyncio helper to run synchronous code in a thread, e.g. asyncio.to_thread or loop.run_in_executor.

async def timer():
    next_run = time.time()
    while True:
        # run blocking function in thread to keep event-loop free
        await asyncio.to_thread(
            sendmail, name, filepath + "\\" + name, receiver
        )
        # pause to run approx every 30 seconds
        await asyncio.sleep(next_run - time.time())
        next_run += 30
Sign up to request clarification or add additional context in comments.

5 Comments

so just to clarify, once runs() reaches "await asyncio.sleep(5)" does it switch to the timer() function for 5 seconds?
Once runs reaches asyncio.sleep(5), runs is suspended for 5 seconds. In the meantime, timer or other coroutines may run if they desire.
MisterMiyagi, shouldn't the last line be asyncio.run(asyncio.gather(...))? The way it's written, an exception in either coroutine will pass silently.
@user4815162342 asyncio.run works only with a coroutine; asyncio.gather provides merely a Future, not a coroutine. The two are not compatible.
Ouch, I keep forgetting about that (IMHO arbitrary and unnecessary) limitation of asyncio.run. The curio/trio concept of requiring coroutines everywhere doesn't work in asyncio where even some of the most basic utilities, like gather, return futures. Anyway, thanks for the edit!
1

You can Use await asycio.sleep() For async wait

import asyncio


async def timer():
    while True:
        
        print("email sent")
        await asyncio.sleep(30)


async def runs():
    while True:
        print("Hello World")
        await asyncio.sleep(5)

loop = asyncio.get_event_loop()
loop.create_task(timer())
loop.create_task(runs())
loop.run_forever()

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.