Call an async function periodically on a Tornado web server? - python

I have a web site using Tornado. And I need to call a function (do_work() in the following exmaple) periodically.
import asyncio
import datetime
from typing import Any, Callable, Coroutine
import tornado
import tornado.ioloop
def schedule_next_sync(loop, func, seconds):
time = datetime.datetime.timestamp(
datetime.datetime.now() + datetime.timedelta(seconds=seconds)
)
def wrapper():
loop.run_until_complete(func())
schedule_next_sync(loop, func, seconds)
print("The next run is sheduled for %s", datetime.datetime.fromtimestamp(time))
tornado.ioloop.IOLoop.current().add_timeout(time, wrapper)
async def do_work(x):
"""To be run periodically"""
print(f"...{datetime.datetime.now()}: {x}")
await asyncio.sleep(1)
print('....')
event_loop = asyncio.get_event_loop()
_func = lambda: do_work("test")
schedule_next_sync(event_loop, _func, 3) # every 3 seconds
tornado.ioloop.IOLoop.current().start()
However, the code got the following error?
The next run is sheduled for %s 2022-03-30 01:48:23.141079
ERROR:tornado.application:Exception in callback functools.partial(<function schedule_next_sync.<locals>.wrapper at 0x000001A7D6B99700>)
Traceback (most recent call last):
File "C:\users\xxx\anaconda3\envs\x\lib\site-packages\tornado\ioloop.py", line 741, in _run_callback
ret = callback()
File ".\test.py", line 23, in wrapper
loop.run_until_complete(func())
File "C:\users\xxx\anaconda3\envs\x\lib\asyncio\base_events.py", line 592, in run_until_complete
self._check_running()
File "C:\users\xxx\anaconda3\envs\x\lib\asyncio\base_events.py", line 552, in _check_running
raise RuntimeError('This event loop is already running')
RuntimeError: This event loop is already running
C:\users\xxx\anaconda3\envs\x\lib\site-packages\tornado\ioloop.py:761: RuntimeWarning: coroutine 'do_work' was never awaited
app_log.error("Exception in callback %r", callback, exc_info=True)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

You cannot use loop.run_until_complete() when the event loop is already running.
To schedule the execution of async code from sync code or to run async code concurrently, you need to use asyncio.create_task().
Your example works for me, if I change the wrapper to this:
def wrapper():
asyncio.create_task(func())
schedule_next_sync(loop, func, seconds)

Related

Python websocket in a thread

I try to run a websocket in python in a thread. Here is the example I made:
import asyncio
import websockets
import threading
class websocket(threading.Thread):
def run(self):
asyncio.get_event_loop().run_until_complete(
websockets.serve(self.echo, 'localhost', 8765))
asyncio.get_event_loop().run_forever()
async def echo(websocket, path):
async for message in websocket:
await websocket.send(message)
ws = websocket()
ws.start()
I get that error:
Exception in thread Thread-1:
Traceback (most recent call last):
File "/usr/lib/python3.6/threading.py", line 916, in _bootstrap_inner
self.run()
File "/home/xavier/dev/3003_mdt/mdt-robotarm/robotarm_server.py", line 7, in run
asyncio.get_event_loop().run_until_complete(
File "/usr/lib/python3.6/asyncio/events.py", line 694, in get_event_loop
return get_event_loop_policy().get_event_loop()
File "/usr/lib/python3.6/asyncio/events.py", line 602, in get_event_loop
% threading.current_thread().name)
RuntimeError: There is no current event loop in thread 'Thread-1'.
Process finished with exit code 0
How can I run the websocket in a thread?
You've got the error because get_event_loop creates the loop automatically only in the main thread. Create the loop manually with new_event_loop like this:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(
websockets.serve(self.echo, "localhost", 8765)
)
loop.close()

Opposite of asyncio.to_thread

How do I run asynchronous function in blocking style? I am not allowed to modify signature of my mock function f1() and can't easy switch to async def and so can't use await expression.
async def cc2():
await asyncio.sleep(1)
await asyncio.to_thread(print, "qwerty")
def f1():
t = asyncio.create_task(cc2())
# wait until the task finished before returning
async def main():
f1() # typical call from many places of a program
asyncio.run(main())
I tried asyncio.get_running_loop().run_until_complete(t), but the hack does not work and I get the next error.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.9/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/usr/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
return future.result()
File "<stdin>", line 2, in main
File "<stdin>", line 3, in f1
File "/usr/lib/python3.9/asyncio/base_events.py", line 618, in run_until_complete
self._check_running()
File "/usr/lib/python3.9/asyncio/base_events.py", line 578, in _check_running
raise RuntimeError('This event loop is already running')
RuntimeError: This event loop is already running
How do I run asynchronous function in blocking style?
If you are in sync code, you can call it with asyncio.run(async_function()). If you are in async code, you can await async_function() or asyncio.create_task(async_function()), with the latter scheduling it to run in the background. You are not allowed to use asyncio.run() (or run_until_complete, even with a newly created event loop object) inside async code because it blocks and could halt the outer event loop.
But if you need it for testing purposes, you can always do something like:
# XXX dangerous - could block the current event loop
with concurrent.futures.ThreadPoolExecutor() as pool:
pool.submit(asyncio.run, async_function()).result()

Return results with asyncio

I have the following code:
async def get_balance(exchange, symbol='BTC/USD'):
freeBalance = await exchange.fetchBalance()
symbol1_balance = freeBalance[symbol.split('/')[0]]
symbol2_balance = freeBalance[symbol.split('/')[1]]
return symbol1_balance, symbol2_balance
async def get_balances(exchanges):
futures = [get_balance(exchange) for exchange in exchanges]
balances = await asyncio.gather(*futures)
return balances
exchanges = [exchange1, exchange2, ...]
loop = asyncio.get_event_loop()
results = loop.run_until_complete(get_balances(exchanges))
But I get the error:
res = loop.run_until_complete(get_balances(exchanges, symbol))
Traceback (most recent call last):
File "C:\Users\Nicolas\Anaconda3\lib\asyncio\base_events.py", line
570, in run_until_complete
self.run_forever()
File "C:\Users\Nicolas\Anaconda3\lib\asyncio\base_events.py", line
525, in run_forever
raise RuntimeError('This event loop is already running')
RuntimeError: This event loop is already running
main:49: RuntimeWarning: coroutine 'binance.fetch_balance' was never awaited RuntimeWarning: Enable tracemalloc to get the object
allocation traceback
main:49: RuntimeWarning: coroutine 'bitfinex.fetch_balance' was never awaited RuntimeWarning: Enable tracemalloc to get the object
allocation traceback
To be clear:
print(exchanges[0].fetchBalance())
<coroutine object binance.fetch_ticker at 0x000001FE6473BA48>
So the function get_balance is already a coroutine.
I managed to run the first asyncio-example-gather except that instead of
asyncio.run(main())
File "C:\Users\Nicolas\Anaconda3\lib\asyncio\runners.py", line 34,
in run
"asyncio.run() cannot be called from a running event loop")
RuntimeError: asyncio.run() cannot be called from a running event loop
But the following works:
loop = asyncio.get_running_loop()
res = loop.create_task(main())
I am pretty knew with asynchronous programming, was more using multithreading before. Do you know what's wrong here please?
Thank you guys!

Avoid traceback after CTRL+C in async

I'm currently exploring async/await in python.
I'd like to start Pyrogram, then run the cronjob function. I created this code:
import asyncio
from pyrogram import Client, Filters, MessageHandler
import time
app = Client("pihome")
async def cronjob():
print("Cronjob started")
while True:
#print(f"time is {time.strftime('%X')}")
print(int(time.time()))
if int(time.strftime('%S')) == 10:
print("SECONDO 10") # Change this with checks in future
await asyncio.sleep(1)
async def main():
print(f"Starting {app.session_name}...\n")
# Pyrogram
await app.start()
print("Pyrogram started")
# Cronjob
loop = asyncio.new_event_loop()
loop.run_until_complete(loop.create_task(await cronjob()))
print("\nDone")
await Client.idle()
await app.stop()
print(f"\nStopping {app.session_name}...")
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
But when i want to stop it with Ctrl+C it gives me this traceback:
^CTraceback (most recent call last):
File "pihome.py", line 39, in <module>
asyncio.get_event_loop().run_until_complete(main())
File "/usr/local/lib/python3.8/asyncio/base_events.py", line 603, in run_until_complete
self.run_forever()
File "/usr/local/lib/python3.8/asyncio/base_events.py", line 570, in run_forever
self._run_once()
File "/usr/local/lib/python3.8/asyncio/base_events.py", line 1823, in _run_once
event_list = self._selector.select(timeout)
File "/usr/local/lib/python3.8/selectors.py", line 468, in select
fd_event_list = self._selector.poll(timeout, max_ev)
KeyboardInterrupt
How can I solve it? Also, try/except blocks seem not working
To run the application, it is better to use asyncio.run(main()). In my opinion, it’s more clear.
Replace loop = asyncio.new_event_loop() by loop = asyncio.get_event_loop(). Because using asyncio.new_event_loop() after asyncio.get_event_loop().run_until_complete(main()) you create a second event loop in main thread which is prohibited. Only one event loop per thread is permitted!
Remove await from this line:
loop.run_until_complete(loop.create_task(await cronjob()))
Because create_task need to Coroutine as the first argument but you pass it None

asyncio create_subprocess_shell python 3.7 on windows

I'm trying to test some asyncio functionality on Windows. I'm using Python 3.7.
This complains bitterly about some NotImplementedError
import asyncio
import os
import time
import sys
#this works
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
#this doesn't
async def test_async(num):
print('Task #{0} start'.format(num))
proc = await asyncio.create_subprocess_shell(
'C:/Python37/python test_repl.py',
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
stdin=asyncio.subprocess.PIPE )
stdout, stderr = await proc.communicate("sleep 10")
cmd = 'python'
print(f'[{cmd!r} exited with {proc.returncode}]')
if stdout:
print(f'[stdout]\n{stdout.decode()}')
if stderr:
print(f'[stderr]\n{stderr.decode()}')
async def test_loop():
task1 = asyncio.create_task(
test_async(1))
task2 = asyncio.create_task(
test_async(2))
print(f"started at {time.strftime('%X')}")
await task1
await task2
print(f"finished at {time.strftime('%X')}")
loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(loop)
asyncio.run(test_loop())
I've tried several loop types (Protractor and Default). I've tried different functions through the shell and directly invoking the program (_shell and _exec in the subprocess lingo). Nothing seems to work. For a much hype I've read about asyncio, it can't possibly be a Linux only thing. I must be doing something wrong. Can you please point me in the right direction.
The specific error I get is the following:
--------------Async--------------------
started at 22:39:55
Task #1 start
Task #2 start
Traceback (most recent call last):
File "multirun.py", line 45, in <module>
asyncio.run(test_loop())
File "C:\Python37\lib\asyncio\runners.py", line 43, in run
return loop.run_until_complete(main)
File "C:\Python37\lib\asyncio\base_events.py", line 568, in run_until_complete
return future.result()
File "multirun.py", line 38, in test_loop
await task1
File "multirun.py", line 20, in test_async
stdin=asyncio.subprocess.PIPE)
File "C:\Python37\lib\asyncio\subprocess.py", line 202, in create_subprocess_shell
stderr=stderr, **kwds)
File "C:\Python37\lib\asyncio\base_events.py", line 1486, in subprocess_shell
protocol, cmd, True, stdin, stdout, stderr, bufsize, **kwargs)
File "C:\Python37\lib\asyncio\base_events.py", line 444, in _make_subprocess_transport
raise NotImplementedError
NotImplementedError
Task exception was never retrieved
future: <Task finished coro=<test_async() done, defined at multirun.py:12> exception=NotImplementedError()>
Traceback (most recent call last):
File "multirun.py", line 20, in test_async
stdin=asyncio.subprocess.PIPE)
File "C:\Python37\lib\asyncio\subprocess.py", line 202, in create_subprocess_shell
stderr=stderr, **kwds)
File "C:\Python37\lib\asyncio\base_events.py", line 1486, in subprocess_shell
protocol, cmd, True, stdin, stdout, stderr, bufsize, **kwargs)
File "C:\Python37\lib\asyncio\base_events.py", line 444, in _make_subprocess_transport
raise NotImplementedError
NotImplementedError
Also, I got the same error when trying to run the notepad.
As of Python 3.8, this issue should no longer exist, as the proactor event loop is now the default event loop on Windows. Original answer follows below.
The problem is that, despite appearances, you're not actually using the ProactorEventLoop. asyncio.run() creates a new event loop based on the current loop creation policy, which you've never changed. Creating a new loop for each run is normally a feature because it guarantees cleanup of the resources associated with the loop - but in this case it's incompatible with the example from the documentation. (Edit: the example has since been removed because ProactorEventLoop was made the default.)
A quick fix is to change asyncio.run(test_loop()) to loop.run_until_complete(test_loop()). A better fix is to set the event loop policy to one that creates the proactor loop.

Categories

Resources