If I have some function, which does a lot of calculations, and it can take a while, is it good to use asyncio.sleep()
between the parts of calculations to release event loop (to prevent blocking event loop)?
import asyncio
async def long_function(a, b, c):
# some calculations
await asyncio.sleep(0) # release event loop
# some another calculations
await asyncio.sleep(0) # release event loop
Is there another, more better way to solve such problems? Some best practices, maybe?
How many times should Asyncio run () be called? It should be used as a main entry point for asyncio programs, and should ideally only be called once. New in version 3.7.
To do so we have to create a new async function (main) and call all the async functions (which we want to run at the same time) in that new function (main). And then call the new (main) function using Event Loops… Code: Python3.
However, to my understanding, Async/IO means the server can only run on one processing core. Regular, synchronous servers like uwsgi , on the other hand, can fully utilize the computer's computing resources with truly parallel threads and processes.
What is Asyncio sleep? The Sleep() Function Of Asyncio In Python The asyncio. sleep() method suspends the execution of a coroutine. Coroutines voluntarily yield CPU leading to co-operative multitasking through the await keyword.
TL;DR just use loop.run_in_executor
to do blocking work
To understand why it doesn't help, let's first make a class
that does something with the event loop. Like:
class CounterTask(object):
def __init__(self):
self.total = 0
async def count(self):
while True:
try:
self.total += 1
await asyncio.sleep(0.001) # Count ~1000 times a second
except asyncio.CancelledError:
return
This will simply count around 1000 times a second, if the event loop is completely open to it.
Just to demonstrate the worst way, let's start the counter task and naively run an expensive function without any thought to the consequences:
async def long_function1():
time.sleep(0.2) # some calculations
async def no_awaiting():
counter = CounterTask()
task = asyncio.create_task(counter.count())
await long_function1()
task.cancel()
print("Counted to:", counter.total)
asyncio.run(no_awaiting())
Output:
Counted to: 0
Well that didn't do any counting! Notice, we never awaited at all. This function is just doing synchronous blocking work. If the counter was able to run in the event loop by itself we should have counted to about 200 in that time. Hmm, so maybe if we split it up and leverage asyncio
to give control back to the event loop it can count? Let's try that...
async def long_function2():
time.sleep(0.1) # some calculations
await asyncio.sleep(0) # release event loop
time.sleep(0.1) # some another calculations
await asyncio.sleep(0) # release event loop
async def with_awaiting():
counter = CounterTask()
task = asyncio.create_task(counter.count())
await long_function2()
task.cancel()
print("Counted to:", counter.total)
asyncio.run(with_awaiting())
Output:
Counted to: 1
Well I guess that's technically better. But ultimately this shows the point: The asyncio
event loop shouldn't do any blocking processing. It is not intended to solve those issues. The event loop is helplessly waiting for your next await
. But the run_in_executor
does provide a solution for this, while keeping our code in the asyncio
style.
def long_function3():
time.sleep(0.2) # some calculations
async def in_executor():
counter = CounterTask()
task = asyncio.create_task(counter.count())
await asyncio.get_running_loop().run_in_executor(None, long_function3)
task.cancel()
print("Counted to:", counter.total)
asyncio.run(in_executor())
Output:
Counted to: 164
Much better! Our loop was able to continue going while our blocking function was doing things as well, by the good old-fashion way of threads.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With