AsyncIO - How to test if my function is blocking? - python

I'm trying to test if I'm correctly using an asyncio library (aiofiles) to see if its blocking.
How can I do this? My idea was to have an infinite loop of asyncio.sleep checking the delay between each call, while I try to insert a blocking call in between
This is my current try, but blocking is never called, I'm assuming the problem is that the infinite loop is actually also blocking.
from asyncio import sleep, get_event_loop
import time
async def test_blocking():
while True:
timea = time.time()
await sleep(1)
timeb = time.time() - timea
delay = timeb - 1.00
if delay >= 0.01:
print(f'Function blocked by {delay}s')
async def blocking():
print('Running blocking function')
time.sleep(5)
loop = get_event_loop()
loop.run_until_complete(test_blocking())
loop.run_until_complete(blocking())

Your test_blocking coroutine is not actually blocking the event loop - it's implemented correctly. However, run_until_complete is a blocking call. So your script is never actually making it to the run_until_complete(blocking()) line - it's waiting for test_blocking to return, which never happens because it's an infinite loop. Use loop.create_task instead, and you'll see the behavior you expect.
Also, you may want to consider using asyncio's debug mode to achieve what you're trying to do here. When enabled, it can alert you when a particular callback takes longer than a given amount of time:
Callbacks taking longer than 100ms are logged. The
loop.slow_callback_duration attribute can be used to set the minimum
execution duration in seconds that is considered “slow”.
When enabled on your test script, it prints this:
Executing <Task finished name='Task-2' coro=<blocking() done, defined at a.py:13> result=None created at /home/dan/.pyenv/versions/3.8.3/lib/python3.8/asyncio/base_events.py:595> took 5.005 seconds

Related

Python Asyncio/Await: Having Trouble understanding the effect of await within a loop

Code Block B is from an Asyncio/Aiohttp Tutorial Video I am trying to follow along
Quick Description:
The async function (The "coroutine") loops through a list of ticker symbols making API call requests for each symbol.
CODE BLOCK B
# GETTING MARKET DATA via API REQUESTS
###### SETUP STUFF ######
api_key = os.getenv('ALPHAVANTAGE_API_KEY')
url = 'https://www.alphavantage.co/query?function=OVERVIEW&symbol={}&apikey={}'
symbols = ['AAPL' , 'GOOG', 'TSLA', 'MSFT', 'AAPL' , 'DWAC' , 'VAXX' , 'MRNA' , 'JNJ']
results = []
###### SETUP STUFF ######
async def get_symbols_async():
##########################
session = aiohttp.ClientSession()
for symbol in symbols:
print('Working on: {}'.format(symbol))
CR = session.get(url.format(symbol, api_key), ssl=False)
# CR: Creates/Is a Coroutine function & does nothing by itself.
response = await CR
# AWAIT: Throws Coroutine in the EVENT LOOP, where they can then run/ be executed.
# ... where Coroutines can MAKE & RETURN Calls.
await session.close()
##########################
asyncio.run(get_symbols_async())
He says: at the end of each iteration within the for loop, the main program/thread has to wait for the response.
The await in Code block A didn't hold up / pause the execution of the next print line.
##################### QUESTION
What I'm seeing is a builtin threading.join() equivalent mechanism in play here. I just don't know why or where it's coming from since I don't know the internal workings of the event loop.
I thought the keyword await placed coroutines into the event loop where they are then queued/scheduled to run/execute ie send out whatever I/O calls. At first I thought this was a misleading keyword since an await statement starts Coroutines, where the response quietly gets stored in its assigned variable (the variable: "response" in this case) without interupting the main, unless the main requires the return value before it is ready. So where exactly is the hangup?
Why is the main thread is being affected/stopped at the end of each for loop iteration (according to guy in video)?
##################### QUESTION
CODE BLOCK A
async def greet():
##########################
print('welcome')
await asyncio.sleep(1)
print('Hello')
##########################
asyncio.run(greet())
seems to pass the coroutine into the event loop without holding up the main.
The object that follows the await keyword is not necessarily a coroutine, but it can be any awaitable object. Coroutines are awaitable objects but there are others, such as asyncio.Future. It is true that awaiting a coroutine is one way of causing it to begin execution, but I don't think it's fair to say that await is therefore a misleading bit of terminology. In all cases it does what it implies: it waits for the awaitable object to enter its "done" state. If it's awaiting a coroutine, the code doesn't proceed until the coroutine has finished execution and becomes "done." While an await expression is waiting, other tasks get a chance to run. If the awaitable is already done and there are no other tasks that are ready to run, there is very little delay at all.
In your code block A, asyncio.sleep(1) returns coroutine object that does nothing but doesn't become "done" for 1 second. So the task gets suspended for one second.
You say that the video said, "at the end of each iteration within the for loop, the main program/thread has to wait for the response." Yes, it will wait for CR to become "done." What is CR? How long will it take to become "done"? If CR is the result of a simple web transaction it might be done in only a few milliseconds. That's still "waiting for a response" but it won't be easy to see. You can measure the delay like this:
t0 = time.time()
response = await CR
print(time.time() - t0)
You will see how long the await was, in seconds.

What does asyncio.create_task actually do?

I'm trying to understand how does asyncio.create_task actually work. Suppose I have following code:
import asyncio
import time
async def delayer():
await asyncio.sleep(1)
async def messenger():
await asyncio.sleep(1)
return "A Message"
async def main():
message = await messenger()
await delayer()
start_time = time.time()
asyncio.run(main())
end_time = time.time() - start_time
print(end_time)
The code will take about 2 seconds. But if I make some changes to the body of main like this:
import asyncio
import time
async def delayer():
await asyncio.sleep(1)
async def messenger():
await asyncio.sleep(1)
return "A Message"
async def main():
task1 = asyncio.create_task(delayer())
task2 = asyncio.create_task(delayer())
await task1
await task2
start_time = time.time()
asyncio.run(main())
end_time = time.time() - start_time
print(end_time)
Now the code will take about 1 second.
My understanding from what I read is that await is a blocking process as we can see from the first code. In that code we need to wait 1 second for the messenger function to return, then another second for delayer function.
Now the real question come from the second code. We just learnt that await need us to wait for its expression to return. So even if we use async.create_task, shouldn't awaits in one of the function's body block the process and then return whenever it finishes its job, thus should give us 2 seconds for the program to end?
If that wasn't the case, can you help me understand the asyncio.create_task?
What I know:
await is a blocking process
await executes coroutine function and task object
await makes us possible to pause coroutine process (I don't quite understand about this, too)
create_task creates task object and then schedule and execute it as soon as possible
What I am expecting:
I hope I can get a simple but effective answer about how does asyncio.create_task conduct its work using my sample code.
Perhaps it will help to think in the following way.
You cannot understand what await does until you understand what an event loop is. This line:
asyncio.run(main())
creates and executes an event loop, which is basically an infinite loop with some methods for allowing an exit - a "semi-infinite" loop, so to speak. Until that loop exits, it will be entirely responsible for executing the program. (Here I am assuming that your program has only a single thread and a single Process. I'm not talking about concurrent program in any form.) Each unit of code that can run within an event loop is called a "Task." The idea of the loop is that it can run multiple Tasks by switching from one to another, thus giving the illusion that the CPU is doing more than one thing at a time.
The asyncio.run() call does a second thing: it creates a Task, main(). At that moment, it's the only Task. The event loop begins to run the Task at its first line. Initially it runs just like any other function:
async def main():
task1 = asyncio.create_task(delayer())
task2 = asyncio.create_task(delayer())
await task1
await task2
It creates two more tasks, task1 and task2. Now there are 3 Tasks but only one of them can be active. That's still main(). Then you come to this line:
await task1
The await keyword is what allows this whole rigmarole to work. It is an instruction to the event loop to suspend the active task right here, at this point, and possibly allow another Task to become the active one. So to address your first bullet point, await is neither "blocking" nor is it a "process". Its purpose is to mark a point at which the event loop gets control back from the active Task.
There is another thing happening here. The object that follows the await is called, unimaginatively, an "awaitable" object. Its crucial property is whether or not it is "done." The event loop keeps track of this object; as the loop cycles through its Tasks it will keep checking this object. If it's not done, main() doesn't resume. (This isn't exactly how it's implemented because that would be inefficient, but it's conceptually what's happening.) If you want to say that the await is "blocking" main() until task1 is finished, that's sort-of true; but "blocking" has a technical meaning so it's not the best word to use. In any case, the event loop is not "blocked" at all - it can keep running other Tasks until the awaitable task1 is done. After task1 becomes "done" and main() gets its turn to be the active task, execution continues to the next line of code.
Your second bullet point, "await executes coroutine function and task object" is not correct. await doesn't execute anything. As I said, it just marks a point where the Task gets suspended and the event loop gets control back. Its awaitable determines when the Task can be resumed.
You say, "await makes [it] possible to pause coroutine process". Not quite right - it ALWAYS suspends the current Task. Whether or not there is a significant delay in the Task's execution depends on whether there are other Tasks that are ready to take over, and also the state of its awaitable.
"create_task creates task object and then schedule and execute it as soon as possible." Correct. But "as soon as possible" means the next time the current Task hits an await expression. Other Tasks may get a turn to run first, before the new Task gets a chance to start. Those details are up to the implementation of the event loop. But eventually the new Task will get a turn.
In the comments you ask, "Is it safe if I say that plain await, not being involved in any event loop or any kind of it, works in blocking manner?" It's absolutely not safe to say that. First of all, there is no such thing as a "plain await". Your task must wait FOR something, otherwise how would the event loop know when to resume? An await without an event loop is either a syntax error or a runtime error - it makes no sense, because await is a point where the Task and the event loop interact. The main point is that event loops and await expression are intimately related: an await without an event loop is an error; an event loop without any await expressions is useless.
The closest you can come to a plain await is this expression:
await asyncio.sleep(0)
which has the effect of suspending the current Task momentarily, giving the event loop a chance to run other tasks, resuming this Task as soon as possible.
One other point is that the code:
await task1
is an expression which has a value, in this case the returned value from task1. Since your task1 doesn't return anything this will be None. But if your delayer function looked like this:
async def delayer():
await asyncio.sleep(1)
return "Hello"
then in main() you could write:
print(await task1)
and you would see "Hello" on the console.

Is this a good alternative of asyncio.sleep

I decided not use asyncio.sleep() and tried to create my own coroutine function as shown below. Since, time.sleep is an IO bound function, I thought this will print 7 seconds. But it prints 11 seconds.
import time
import asyncio
async def my_sleep(delay):
time.sleep(delay)
async def main():
start = time.time()
await asyncio.gather(my_sleep(4), my_sleep(7))
print("Took", time.time()-start, "seconds")
asyncio.run(main())
# Expected: Took 7 seconds
# Got: Took 11.011508464813232 seconds
Though if I write a similar code with threads, It does print 7 seconds. Do Task objects created by asyncio.gather not recognize time.sleep as an IO bound operation, the way threads do? Please explain why is it happening.
time.sleep is blocking operation for event loop. It has no sense if you write async in defention of function because it not unlock the event loop (no await command)
This two questions might help you to understand more:
Python 3.7 - asyncio.sleep() and time.sleep()
Run blocking and unblocking tasks together with asyncio
This would not work for you because time.sleep is a synchronous function.
From the 'perspective' of the event loop my_sleep might as well be doing a heavy computation within an async function, never yielding the execution context while working.
The first tell tale sign of this is that you're not using an await statement when calling time.sleep.
Making a synchronous function behave as an async one is not trivial, but the common approach is moving the function call to worker threads and awaiting the results.
I'd recommend looking at the solution of anyio, they implemented a run_sync function which does exactly that.

asyncio cancel task and related function run via run_in_executor

Just can't wrap my head around solving this issue, so maybe someone here can enlighten me or maybe even tell me that what I want to achieve isn't possible. :)
Problem statement:
I have an asyncio event loop, on that loop I create a task by supplying my asynchronous coroutine work(). I could then go ahead and cancel the task by invoking its cancel() method - this works.
But in my very special case, the asynchronous task itself spawns another operation, which is an underlying blocking / synchronous function.
What happens now, if I decide to cancel the task, is that my asynchronous work() function will be cancelled appropriately, however, the synchronous function is still going to be executed as if nothing ever happened.
I tried to make an example as simple as possible to illustrate my problem:
import asyncio
import time
def sync_work():
time.sleep(10)
print("sync work completed")
return "sync_work_result"
async def work(loop):
result = await loop.run_in_executor(None, sync_work)
print(f"sync_work {result}")
print("work completed")
async def main(loop):
t1 = loop.create_task(work(loop))
await asyncio.sleep(4)
t1.cancel()
loop = asyncio.get_event_loop()
try:
asyncio.ensure_future(main(loop))
loop.run_forever()
except KeyboardInterrupt:
pass
finally:
print("loop closing")
loop.close()
This will print out sync work completed after about 10 seconds.
How would I invoke the synchronous function in a way, that would allow me to terminate it once my asynchronous task is cancelled? The tricky part is, that I would not have control over sync_work() as this comes from another external package.
I'm open to other approaches of calling my synchronous function from an asynchronous function that would allow it to be terminated properly in some kind of way.

How asyncio understands that task is complete for non-blocking operations

I'm trying to understand how asyncio works. As for I/O operation i got understand that when await was called, we register Future object in EventLoop, and then calling epoll for get sockets which belongs to Future objects, that ready for give us data. After we run registred callback and resume function execution.
But, the thing that i cant understant, what's happening if we use await not for I/O operation. How eventloop understands that task is complete? Is it create socket for that or use another kind of loop? Is it use epoll? Or doesnt it add to Loop and used it as generator?
There is an example:
import asyncio
async def test():
return 10
async def my_coro(delay):
loop = asyncio.get_running_loop()
end_time = loop.time() + delay
while True:
print("Blocking...")
await test()
if loop.time() > end_time:
print("Done.")
break
async def main():
await my_coro(3.0)
asyncio.run(main())
await doesn't automatically yield to the event loop, that happens only when an async function (anywhere in the chain of awaits) requests suspension, typically due to IO or timeout not being ready.
In your example the event loop is never returned to, which you can easily verify by moving the "Blocking" print before the while loop and changing main to await asyncio.gather(my_coro(3.0), my_coro(3.0)). What you'll observe is that the coroutines are executed in series ("blocking" followed by "done", all repeated twice), not in parallel ("blocking" followed by another "blocking" and then twice "done"). The reason for that was that there was simply no opportunity for a context switch - my_coro executed in one go as if they were an ordinary function because none of its awaits ever chose to suspend.

Categories