Making asynchronous functions in Python - python

I have 4 functions that I wrote in python. I want to make first 3 functions asynchronous.
So it should look like this:
x = 3.13
def one(x):
return x**x
def two(x):
return x*x*12-314
def three(x):
return x+x+352+x**x
def final(x):
one = one(x)
two = two(x)
three = three(x)
return one, two, three
This is what I did:
async def one(x):
return x**x
async def two(x):
return x*x*12-314
async def three(x):
return x+x+352+x**x
def final(x):
loop = asyncio.get_event_loop()
one = loop.create_task(one(x))
two = loop.create_task(two(x))
three = loop.create_task(three(x))
#loop.run_until_complete('what should be here')
#loop.close()
return one, two, three
But I get this error (if lines above are not commented):
RecursionError: maximum recursion depth exceeded
I do not know what is wrong (Im new to this), I have also tried to add this:
await asyncio.wait([one,two,three])
but to be honest I do not know where and why should I add it.
Without that, my code works but it does not give me result, it prints this:
(<Task pending name='Task-1' coro=<one() running at /Users/.../Desktop/....py:63>>, <Task pending name='Task-2' coro=<two() running at /Users/.../Desktop/...py:10>>, <Task pending name='Task-3' coro=<three() running at /Users/.../Desktop/...py:91>>)
Any help?

The major purpose of async syntax and its libraries is to hide the details of event loops. Prefer to use high-level APIs directly:
def final(x):
loop = asyncio.get_event_loop()
return loop.run_until_complete( # run_until_complete fetches the task results
asyncio.gather(one(x), two(x), three(x)) # gather runs multiple tasks
)
print(final(x)) # [35.5675357348548, -196.43720000000002, 393.8275357348548]
If you want to explicitly control concurrency, i.e. when functions are launched as tasks, it is simpler to do this inside another async function. In your example, making final an async function simplifies things by giving direct access to await and the ambient loop:
async def final(x): # async def – we can use await inside
# create task in whatever loop this function runs in
task_one = asyncio.create_task(one(x))
task_two = asyncio.create_task(two(x))
task_three = asyncio.create_task(three(x))
# wait for completion and fetch result of each task
return await task_one, await task_two, await task_three
print(asyncio.run(final(x))) # (35.5675357348548, -196.43720000000002, 393.8275357348548)

Related

Asyncio.sleep causes script to End Immediately

In my simple asyncio Python program below, bar_loop is supposed to run continuously with a 1 second delay between loops.
Things run as expected when we have simply
async def bar_loop(self):
while True:
print('bar')
However, when we add a asyncio.sleep(1), the loop will end instead of looping.
async def bar_loop(self):
while True:
print('bar')
await asyncio.sleep(1)
Why does asyncio.sleep() cause bar_loop to exit immediately? How can we let it loop with a 1 sec delay?
Full Example:
import asyncio
from typing import Optional
class Foo:
def __init__(self):
self.bar_loop_task: Optional[asyncio.Task] = None
async def start(self):
self.bar_loop_task = asyncio.create_task(self.bar_loop())
async def stop(self):
if self.bar_loop_task is not None:
self.bar_loop_task.cancel()
async def bar_loop(self):
while True:
print('bar')
await asyncio.sleep(1)
if __name__ == '__main__':
try:
foo = Foo()
asyncio.run(foo.start())
except KeyboardInterrupt:
asyncio.run(foo.stop())
Using Python 3.9.5 on Ubuntu 20.04.
This behavior has nothing to do with calling asyncio.sleep, but with the expected behavior of creating a task and doing nothing else.
Tasks will run in parallel in the the asyncio loop, while other code that uses just coroutine and await expressions can be thought as if run in a linear pattern - however, as the are "out of the way" of the - let's call it "visible path of execution", they also won't prevent that flow.
In this case, your program simply reaches the end of the start method, with nothing left being "awaited", the asyncio loop simply finishes its execution.
If you have no explicit code to run in parallel to bar_loop, just await for the task. Change your start method to read:
async def start(self):
self.bar_loop_task = asyncio.create_task(self.bar_loop())
try:
await self.bar_loop_task
except XXX:
# handle excptions that might have taken place inside the task

run expensive computation in another thread in blender

I'm developing a blender operator that needs to run an expensive for loop to generate a procedural animation. It looks roughly like this:
class MyOperator(bpy.types.Operator):
def execute(self, context):
data = []
for _ in range(10000):
data.append(run_something_expensive())
instantiate_data_into_blender(data)
return {"FINISHED"}
The problem of course is that when I run it from blender it takes a lot of time to finish and the UI becomes unresponsive. What is the recommended way to handle this? Is there a way to run this computation in another thread and potentially update the blender scene as the results get generated? (i.e. running instantiate_data_into_blender every once in a while as data becomes available)
Take a look at the asyncio module.
First: you have to import asyncio and define a loop.
import asyncio
loop = asyncio.get_event_loop()
Second: you create an async method in your operator.
async def massive_work(self, param):
returnedData = await your.longrun.func(param)
if returnedData == '':
return False
return True
Third: call the async method from your execute method
allDone = loop.run_until_complete(massive_work(dataToWorkOn))
if allDone != True:
self.report({'ERROR'}, "something went wrong!")
return {'CANCELLED'}
else:
self.report({'INFO'}, "all done!")
return {'FINISHED'}

await does not return the value of a future with the value set in a thread

This code should print "hi" 3 times, but it doesn't always print.
I made a gif that shows the code being executed:
from asyncio import get_event_loop, wait_for, new_event_loop
from threading import Thread
class A:
def __init__(self):
self.fut = None
def start(self):
"""
Expects a future to be created and puts "hi" as a result
"""
async def foo():
while True:
if self.fut:
self.fut.set_result('hi')
self.fut = None
new_event_loop().run_until_complete(foo())
async def make(self):
"""
Create a future and print your result when it runs out
"""
future = get_event_loop().create_future()
self.fut = future
print(await future)
a = A()
Thread(target=a.start).start()
for _ in range(3):
get_event_loop().run_until_complete(a.make())
This is caused by await future, because when I change
print(await future)
by
while not future.done():
pass
print(future.result())
the code always prints "hi" 3 times.
Is there anything in my code that causes this problem in await future?
Asyncio functions are not thread-safe, except where explicitly noted. For set_result to work from another thread, you'd need to call it through call_soon_threadsafe.
But in your case this wouldn't work because A.start creates a different event loop than the one the main thread executes. This creates issues because the futures created in one loop cannot be awaited in another one. Because of this, and also because there is just no need to create multiple event loops, you should pass the event loop instance to A.start and use it for your async needs.
But - when using the event loop from the main thread, A.start cannot call run_until_complete() because that would try to run an already running event loop. Instead, it must call asyncio.run_coroutine_threadsafe to submit the coroutine to the event loop running in the main thread. This will return a concurrent.futures.Future (not to be confused with an asyncio Future) whose result() method can be used to wait for it to execute and propagate the result or exception, just like run_until_complete() would have done. Since foo will now run in the same thread as the event loop, it can just call set_result without call_soon_threadsafe.
One final problem is that foo contains an infinite loop that doesn't await anything, which blocks the event loop. (Remember that asyncio is based on cooperative multitasking, and a coroutine that spins without awaiting doesn't cooperate.) To fix that, you can have foo monitor an event that gets triggered when a new future is available.
Applying the above to your code can look like this, which prints "hi" three times as desired:
import asyncio
from asyncio import get_event_loop
from threading import Thread
class A:
def __init__(self):
self.fut = None
self.have_fut = asyncio.Event()
def start(self, loop):
async def foo():
while True:
await self.have_fut.wait()
self.have_fut.clear()
if self.fut:
self.fut.set_result('hi')
self.fut = None
asyncio.run_coroutine_threadsafe(foo(), loop).result()
async def make(self):
future = get_event_loop().create_future()
self.fut = future
self.have_fut.set()
print(await future)
a = A()
Thread(target=a.start, args=(get_event_loop(),), daemon=True).start()
for _ in range(3):
get_event_loop().run_until_complete(a.make())

looping over multiple async_generator objects together

I started working out some problems to master python asyncio module. What I wanted to create is a 'clock' which basically just prints out the time after program started in Hour:Minute:Second format. I thought of making three async_generators and looping over those three async_generators using a for loop in a seperate async method. Using builtin zip() method for this gives me the following error.
TypeError: zip argument #1 must support iteration
code :
import asyncio
second = 1
async def seconds():
while True:
for i in range(1,61):
await asyncio.sleep(second)
yield i
async def minutes():
while True:
for i in range(1,61):
await asyncio.sleep(60*second)
yield i
async def hours():
while True:
for i in range(1,61):
await asyncio.sleep(60*60*second)
yield i
async def clock():
for s,m,h in zip(seconds(),minutes(),hours()):
print('{0}:H{1}:M{2}:S'.format(h,m,s))
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(clock())
My question is, doesn't async_genertator objects support iteration? When I checked, i could see that async_generator object hours() has ____aiter____ method. Isn't ____aiter____ iterable? What is wrong with the code I wrote.

Running an event loop within its own thread

I'm playing with Python's new(ish) asyncio stuff, trying to combine its event loop with traditional threading. I have written a class that runs the event loop in its own thread, to isolate it, and then provide a (synchronous) method that runs a coroutine on that loop and returns the result. (I realise this makes it a somewhat pointless example, because it necessarily serialises everything, but it's just as a proof-of-concept).
import asyncio
import aiohttp
from threading import Thread
class Fetcher(object):
def __init__(self):
self._loop = asyncio.new_event_loop()
# FIXME Do I need this? It works either way...
#asyncio.set_event_loop(self._loop)
self._session = aiohttp.ClientSession(loop=self._loop)
self._thread = Thread(target=self._loop.run_forever)
self._thread.start()
def __enter__(self):
return self
def __exit__(self, *e):
self._session.close()
self._loop.call_soon_threadsafe(self._loop.stop)
self._thread.join()
self._loop.close()
def __call__(self, url:str) -> str:
# FIXME Can I not get a future from some method of the loop?
future = asyncio.run_coroutine_threadsafe(self._get_response(url), self._loop)
return future.result()
async def _get_response(self, url:str) -> str:
async with self._session.get(url) as response:
assert response.status == 200
return await response.text()
if __name__ == "__main__":
with Fetcher() as fetcher:
while True:
x = input("> ")
if x.lower() == "exit":
break
try:
print(fetcher(x))
except Exception as e:
print(f"WTF? {e.__class__.__name__}")
To avoid this sounding too much like a "Code Review" question, what is the purpose of asynchio.set_event_loop and do I need it in the above? It works fine with and without. Moreover, is there a loop-level method to invoke a coroutine and return a future? It seems a bit odd to do this with a module level function.
You would need to use set_event_loop if you called get_event_loop anywhere and wanted it to return the loop created when you called new_event_loop.
From the docs
If there’s need to set this loop as the event loop for the current context, set_event_loop() must be called explicitly.
Since you do not call get_event_loop anywhere in your example, you can omit the call to set_event_loop.
I might be misinterpreting, but i think the comment by #dirn in the marked answer is incorrect in stating that get_event_loop works from a thread. See the following example:
import asyncio
import threading
async def hello():
print('started hello')
await asyncio.sleep(5)
print('finished hello')
def threaded_func():
el = asyncio.get_event_loop()
el.run_until_complete(hello())
thread = threading.Thread(target=threaded_func)
thread.start()
This produces the following error:
RuntimeError: There is no current event loop in thread 'Thread-1'.
It can be fixed by:
- el = asyncio.get_event_loop()
+ el = asyncio.new_event_loop()
The documentation also specifies that this trick (creating an eventloop by calling get_event_loop) only works on the main thread:
If there is no current event loop set in the current OS thread, the OS thread is main, and set_event_loop() has not yet been called, asyncio will create a new event loop and set it as the current one.
Finally, the docs also recommend to use get_running_loop instead of get_event_loop if you're on version 3.7 or higher

Categories