two async tasks - one depends on another in python - python

I need to write a code where i need to to check in real time a status of some variable. I decited to use asyncio to create two async def functions
import asyncio
async def one():
global flag
flag = True
while flag == True:
await asyncio.sleep(0.2)
print("Doing one")
async def two():
await asyncio.sleep(2)
global flag
flag = False
async def main():
tasks = []
tasks.append(one())
tasks.append(two())
await asyncio.gather(*tasks)
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(main())
finally:
loop.close()
print("Loop ended")
When loop starts, all tasks has been lauched and after 2 seconds def two() sets flag=False, which stops def one(). It's good but i want def one() to perform while loop without await asyncio.sleep(0.2) becouse i dont want to have real live update so i set await asyncio.sleep(0.0).
Is it a good practice?

Using a global variable is indeed bad practice. What you are looking for is asyncio's primitives, specifically the asyncio.Event primitive. Here is what you are doing, but with asyncio.Event:
import asyncio
async def one(event):
while event.is_set() == False:
await asyncio.sleep(0.5)
print("Hello World!")
async def two(event):
await asyncio.sleep(2)
event.set()
async def main():
event = asyncio.Event()
await asyncio.gather(*[one(event), two(event)])
asyncio.run(main())

Related

running async code within a function which contains blocking code

import asyncio
import time
def blocking_function():
print("Blocking function called")
time.sleep(5)
print("Blocking function finished")
async def concurrent_function():
for x in range(10):
print(x)
await asyncio.sleep(1)
async def main():
print("Main function called")
loop = asyncio.get_event_loop()
loop.run_in_executor(None, blocking_function)
await concurrent_function()
print("Main function finished")
if __name__ == "__main__":
asyncio.run(main())
When attempting to run a blocking function while asyncio code is also running, this works perfectly. However, if I have a funciton which contains blocking code (eg. a libary which is not asyncronous I am unable to do this)
Because the function contains both blocking code, and asynconrous code I cannot use run in executor. How can I get around this?
The below code unfortunetly errors due to the blocking_function not being awaited, but you annot use that in conjunction with run_in_executor
async def blocking_function():
for x in range(4):
print("Blocking function called")
time.sleep(1)
print("Blocking function finished")
print("Async code running:")
await asyncio.sleep(1)
print("Async code finished")
async def concurrent_function():
for x in range(10):
print(x)
await asyncio.sleep(1)
async def main():
print("Main function called")
loop = asyncio.get_event_loop()
loop.run_in_executor(None, blocking_function)
await concurrent_function()
print("Main function finished")
if __name__ == "__main__":
asyncio.run(main())
just create an event loop in the second thread and run the async function there.
import asyncio
import time
async def blocking_function():
for x in range(4):
print("Blocking function called")
time.sleep(1)
print("Blocking function finished")
print("Async code running:")
await asyncio.sleep(1)
print("Async code finished")
def async_blocking_function_runner(func):
# creates another event loop in the other thread and runs func in it
res = asyncio.run(func())
return res
async def concurrent_function():
for x in range(10):
print(x)
await asyncio.sleep(1)
async def main():
print("Main function called")
loop = asyncio.get_event_loop()
res = loop.run_in_executor(None, async_blocking_function_runner, blocking_function)
await concurrent_function()
await res # make sure the task in second thread is done
print("Main function finished")
if __name__ == "__main__":
asyncio.run(main())

Correct destruction process for async code running in a thread

Below is (working) code for a generic websocket streamer.
It creates a daemon thread from which performs asyncio.run(...).
The asyncio code spawns 2 tasks, which never complete.
How to correctly destroy this object?
One of the tasks is executing a keepalive 'ping', so I can easily exit that loop using a flag. But the other is blocking on a message from the websocket.
import json
import aiohttp
import asyncio
import gzip
import asyncio
from threading import Thread
class WebSocket:
KEEPALIVE_INTERVAL_S = 10
def __init__(self, url, on_connect, on_msg):
self.url = url
self.on_connect = on_connect
self.on_msg = on_msg
self.streams = {}
self.worker_thread = Thread(name='WebSocket', target=self.thread_func, daemon=True).start()
def thread_func(self):
asyncio.run(self.aio_run())
async def aio_run(self):
async with aiohttp.ClientSession() as session:
self.ws = await session.ws_connect(self.url)
await self.on_connect(self)
async def ping():
while True:
print('KEEPALIVE')
await self.ws.ping()
await asyncio.sleep(WebSocket.KEEPALIVE_INTERVAL_S)
async def main_loop():
async for msg in self.ws:
def extract_data(msg):
if msg.type == aiohttp.WSMsgType.BINARY:
as_bytes = gzip.decompress(msg.data)
as_string = as_bytes.decode('utf8')
as_json = json.loads(as_string)
return as_json
elif msg.type == aiohttp.WSMsgType.TEXT:
return json.loads(msg.data)
elif msg.type == aiohttp.WSMsgType.ERROR:
print('⛔️ aiohttp.WSMsgType.ERROR')
return msg.data
data = extract_data(msg)
self.on_msg(data)
# May want this approach if we want to handle graceful shutdown
# W.task_ping = asyncio.create_task(ping())
# W.task_main_loop = asyncio.create_task(main_loop())
await asyncio.gather(
ping(),
main_loop()
)
async def send_json(self, J):
await self.ws.send_json(J)
I'd suggest the use of asyncio.run_coroutine_threadsafe instead of asyncio.run. It returns a concurrent.futures.Future object which you can cancel:
def thread_func(self):
self.future = asyncio.run_coroutine_threadsafe(
self.aio_run(),
asyncio.get_event_loop()
)
# somewhere else
self.future.cancel()
Another approach would be to make ping and main_loop a task, and cancel them when necessary:
# inside `aio_run`
self.task_ping = asyncio.create_task(ping())
self.main_loop_task = asyncio.create_task(main_loop())
await asyncio.gather(
self.task_ping,
self.main_loop_task
return_exceptions=True
)
# somewhere else
self.task_ping.cancel()
self.main_loop_task.cancel()
This doesn't change the fact that aio_run should also be called with asyncio.run_coroutine_threadsafe. asyncio.run should be used as a main entry point for asyncio programs and should be only called once.
I would like to suggest one more variation of the solution. When finishing coroutines (tasks), I prefer minimizing the use of cancel() (but not excluding), since sometimes it can make it difficult to debug business logic (keep in mind that asyncio.CancelledError does not inherit from an Exception).
In your case, the code might look like this(only changes):
class WebSocket:
KEEPALIVE_INTERVAL_S = 10
def __init__(self, url, on_connect, on_msg):
# ...
self.worker_thread = Thread(name='WebSocket', target=self.thread_func)
self.worker_thread.start()
async def aio_run(self):
self._loop = asyncio.get_event_loop()
# ...
self._ping_task = asyncio.create_task(ping())
self._main_task = asyncio.create_task(main_loop())
await asyncio.gather(
self._ping_task,
self._main_task,
return_exceptions=True
)
# ...
async def stop_ping(self):
self._ping_task.cancel()
try:
await self._ping_task
except asyncio.CancelledError:
pass
async def _stop(self):
# wait ping end before socket closing
await self.stop_ping()
# lead to correct exit from `async for msg in self.ws`
await self.ws.close()
def stop(self):
# wait stopping ping and closing socket
asyncio.run_coroutine_threadsafe(
self._stop(), self._loop
).result()
self.worker_thread.join() # wait thread finish

Manage asyncio coroutines in dict

I want to manage some coroutines in a dict, while running a main coroutine.
Specifically I want to start endless coroutines, put the handler of them in a dict and cancel them via a dict call again. In my example I want to start 4 Coroutines and they shall cancel one by one again with the coroutine doomsday. I'm using Python 3.6.
import asyncio
import traceback
async def add_to_handler(node, func):
func_handler[node] = asyncio.ensure_future(func, loop=loop)
return
async def test_func1():
while True:
print("1 - HeNlO")
await asyncio.sleep(1)
async def test_func2():
while True:
print("2 - TestFunc2")
await asyncio.sleep(2)
async def test_func3():
while True:
print("3 - Tukan")
await asyncio.sleep(3)
async def test_func4():
while True:
print("4 - Do Coro!")
await asyncio.sleep(4)
async def doomsday():
# Cancel coroutine every 10 seconds
print("launch doomsday")
for i in range(len(func_handler)):
await asyncio.sleep(10)
print("start cancelling with {}".format(i))
func_handler[str(i + 1)].cancel()
return
async def main():
await add_to_handler("1", test_func1)
await add_to_handler("2", test_func2)
await add_to_handler("3", test_func3)
await add_to_handler("4", test_func4)
await doomsday()
while True:
print("z..z..Z..Z...Z")
print(func_handler)
await asyncio.sleep(5)
func_handler = {}
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(main())
except KeyboardInterrupt:
print("stop loop")
loop.close()
I tried it with the .call_latermethod of AbstractEventLoop instead of an endless while loop, but it still doesn't want to work and it seems, that my coroutines are seen as function, but I don't know why. Where is my fault?
try to change this function:
async def add_to_handler(node, func):
func_handler[node] = asyncio.ensure_future(func(), loop=loop)
return None
pay attention on asyncio.ensure_future(func(),loop=loop)

Is there a special syntax for suspending a coroutine until a condition is met?

I need to suspend a coroutine until a condition is met. Currently, I have:
class Awaiter:
def __init__(self):
self.ready = False
def __await__(self):
while not self.ready:
yield
And the caller code:
await awaiter
This works, but it requires boilerplate code. Is it necessary boilerplate or is there a special syntax to await on a predicate, such as:
await condition
which would yield until condition is false?
At the asyncio package there is a builtin Condition object that you can use.
An asyncio condition primitive can be used by a task to wait for some event to happen and then get exclusive access to a shared resource.
How to use the condition (from the same source):
cond = asyncio.Condition()
# The preferred way to use a Condition is an async with statement
async with cond:
await cond.wait()
# It can also be used as follow
await cond.acquire()
try:
await cond.wait()
finally:
cond.release()
A code example:
import asyncio
cond = asyncio.Condition()
async def func1():
async with cond:
print('It\'s look like I will need to wait')
await cond.wait()
print('Now it\'s my turn')
async def func2():
async with cond:
print('Notifying....')
cond.notify()
print('Let me finish first')
# Main function
async def main(loop):
t1 = loop.create_task(func1())
t2 = loop.create_task(func2())
await asyncio.wait([t1, t2])
if __name__ == '__main__':
l = asyncio.get_event_loop()
l.run_until_complete(main(l))
l.close()
This will results with:
It's look like I will need to wait
Notifying....
Let me finish first
Now it's my turn
An alternative way is to use the asyncio.Event.
import asyncio
event = asyncio.Event()
async def func1():
print('It\'s look like I will need to wait')
await event.wait()
print('Now it\'s my turn')
async def func2():
print('Notifying....')
event.set()
print('Let me finish first')
It will have the same results as the Condition code example.

run async while loop independently

Is it possible to run an async while loop independently of another one?
Instead of the actual code I isolated the issue I am having in the following example code
import asyncio, time
class Time:
def __init__(self):
self.start_time = 0
async def dates(self):
while True:
t = time.time()
if self.start_time == 0:
self.start_time = t
yield t
await asyncio.sleep(1)
async def printer(self):
while True:
print('looping') # always called
await asyncio.sleep(self.interval)
async def init(self):
async for i in self.dates():
if i == self.start_time:
self.interval = 3
await self.printer()
print(i) # Never Called
loop = asyncio.get_event_loop()
t = Time()
loop.run_until_complete(t.init())
Is there a way to have the print function run independently so print(i) gets called each time?
What it should do is print(i) each second and every 3 seconds call self.printer(i)
Essentially self.printer is a separate task that does not need to be called very often, only every x seconds(in this case 3).
In JavaScript the solution is to do something like so
setInterval(printer, 3000);
EDIT: Ideally self.printer would also be able to be canceled / stopped if a condition or stopping function is called
The asyncio equivalent of JavaScript's setTimeout would be asyncio.ensure_future:
import asyncio
async def looper():
for i in range(1_000_000_000):
print(f'Printing {i}')
await asyncio.sleep(0.5)
async def main():
print('Starting')
future = asyncio.ensure_future(looper())
print('Waiting for a few seconds')
await asyncio.sleep(4)
print('Cancelling')
future.cancel()
print('Waiting again for a few seconds')
await asyncio.sleep(2)
print('Done')
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())
You'd want to register your self.printer() coroutine as a separate task; pass it to asyncio.ensure_future() rather than await on it directly:
asyncio.ensure_future(self.printer())
By passing the coroutine to asyncio.ensure_future(), you put it on the list of events that the loop switches between as each awaits on further work to be completed.
With that change, your test code outputs:
1516819094.278697
looping
1516819095.283424
1516819096.283742
looping
1516819097.284152
# ... etc.
Tasks are the asyncio equivalent of threads in a multithreading scenario.

Categories