running async code within a function which contains blocking code - python

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())

Related

await while loop in asyncio

How to await while loop? I have the following code snippet:
import asyncio
from asyncio.tasks import sleep
import time
running = True
async def f1():
global running
print(f"Running: {running}")
print("f1() started.")
### await the while loop below
while running:
print(f"Inside while loop")
###
print(f"Running: {running}")
print("f1() ended")
async def f2():
global running
print("f2() started.")
# this should be blocking code
time.sleep(0.5)
print("f2 ended.")
running = False
async def main():
await asyncio.gather(f1(), f2())
asyncio.run(main())
Basically, what I want is:
# 1. Start f1(). f1() has a while loop.
# 2. Start f2().
# 3. Notify f1() to break the while loop.
I guess you have to put in some await asyncio.sleep(0) or so into the while loop, not sure if it works with 0 but just put a small number. It just has to be some await call that gives others the chance to do
import asyncio
from asyncio.tasks import sleep
import time
running = True
async def f1():
global running
print(f"Running: {running}")
print("f1() started.")
### await the while loop below
while running:
print(f"Inside while loop")
await asyncio.sleep(0)
###
print(f"Running: {running}")
print("f1() ended")
async def f2():
global running
print("f2() started.")
# this should be blocking code
time.sleep(0.5)
print("f2 ended.")
running = False
async def main():
await asyncio.gather(f1(), f2())
asyncio.run(main())
that works for me
Without globals:
import asyncio
from asyncio.tasks import sleep
import time
async def f1( q ):
print("f1() started.")
### await the while loop below
## Start listener
event = asyncio.ensure_future( q.get() )
while True:
print( "Inside While Loop" )
if event.done():
break
else:
await asyncio.sleep( 0.01 )
###
print("f1() ended")
async def f2( q ):
print("f2() started.")
# this should be blocking code
time.sleep( 3 )
print("f2 ended.")
await q.put( 1 )
async def main():
q = asyncio.Queue( 1 )
await asyncio.gather(f1( q ), f2( q ))
asyncio.run(main())
But, since f2 is blocking it wont actually print "Inside event loop" while it is being blocked
So you'll get output like
f1() started.
Inside While Loop
f2() started.
f2 ended.
Inside While Loop
f1() ended
Also you can put just about anything in that queue, doesnt have to be 1.

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)

two async tasks - one depends on another in 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())

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.

How to wait for object to change state

Inside my async handler I want to wait until task's state is changed. For now, I just check the state in an endless loop and wait. Here is an example, the wait_until_done function:
import asyncio
class LongTask:
state = 'PENDING'
my_task = LongTask()
def done():
my_task.state = 'DONE'
async def wait_until_done():
while True:
if my_task.state == 'PENDING':
await asyncio.sleep(2)
else:
break
print("Finally, the task is done")
def main(loop, *args, **kwargs):
asyncio.ensure_future(wait_until_done())
loop.call_later(delay=5, callback=done)
loop = asyncio.get_event_loop()
main(loop)
loop.run_forever()
Is there a better way for doing that?
Just to avoid confusion: I guess you are not talking about asyncio.Task, but some variable state instead, right?
In this case you have Future and synchronization primitives that allows you to wait some thing changed asynchronously.
If you need to switch between two states, asyncio.Event is probably what you want. Here's little examle:
import asyncio
my_task = asyncio.Event()
def done():
my_task.set()
async def wait_until_done():
await my_task.wait() # await until event would be .set()
print("Finally, the task is done")
async def main():
loop.call_later(delay=5, callback=done)
await wait_until_done()
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(main())
finally:
loop.run_until_complete(loop.shutdown_asyncgens())
loop.close()
Upd:
More complex example that keeps LongTask interface:
import asyncio
class LongTask:
_event = asyncio.Event()
#property
def state(self):
return 'PENDING' if not type(self)._event.is_set() else 'DONE'
#state.setter
def state(self, val):
if val == 'PENDING':
type(self)._event.clear()
elif val == 'DONE':
type(self)._event.set()
else:
raise ValueError('Bad state value.')
async def is_done(self):
return (await type(self)._event.wait())
my_task = LongTask()
def done():
my_task.state = 'DONE'
async def wait_until_done():
await my_task.is_done()
print("Finally, the task is done")
async def main():
loop.call_later(delay=5, callback=done)
await wait_until_done()
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(main())
finally:
loop.run_until_complete(loop.shutdown_asyncgens())
loop.close()

Categories