Je migre de tornado
vers asyncio
, et je ne trouve pas l'équivalent asyncio
de tornado
de PeriodicCallback
. (Un PeriodicCallback
prend deux arguments: la fonction à exécuter et le nombre de millisecondes entre les appels.)
asyncio
?RecursionError
après un certain temps?Pour Python versions antérieures à 3.5:
import asyncio
@asyncio.coroutine
def periodic():
while True:
print('periodic')
yield from asyncio.sleep(1)
def stop():
task.cancel()
loop = asyncio.get_event_loop()
loop.call_later(5, stop)
task = loop.create_task(periodic())
try:
loop.run_until_complete(task)
except asyncio.CancelledError:
pass
Pour Python 3.5 et supérieur:
import asyncio
async def periodic():
while True:
print('periodic')
await asyncio.sleep(1)
def stop():
task.cancel()
loop = asyncio.get_event_loop()
loop.call_later(5, stop)
task = loop.create_task(periodic())
try:
loop.run_until_complete(task)
except asyncio.CancelledError:
pass
Lorsque vous sentez que quelque chose doit arriver "en arrière-plan" de votre programme asyncio, asyncio.Task
pourrait être un bon moyen de le faire. Vous pouvez lire cet article pour voir comment travailler avec des tâches.
Voici l'implémentation possible de la classe qui exécute une fonction périodiquement:
import asyncio
from contextlib import suppress
class Periodic:
def __init__(self, func, time):
self.func = func
self.time = time
self.is_started = False
self._task = None
async def start(self):
if not self.is_started:
self.is_started = True
# Start task to call func periodically:
self._task = asyncio.ensure_future(self._run())
async def stop(self):
if self.is_started:
self.is_started = False
# Stop task and await it stopped:
self._task.cancel()
with suppress(asyncio.CancelledError):
await self._task
async def _run(self):
while True:
await asyncio.sleep(self.time)
self.func()
Testons le:
async def main():
p = Periodic(lambda: print('test'), 1)
try:
print('Start')
await p.start()
await asyncio.sleep(3.1)
print('Stop')
await p.stop()
await asyncio.sleep(3.1)
print('Start')
await p.start()
await asyncio.sleep(3.1)
finally:
await p.stop() # we should stop task finally
if __== '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Sortie:
Start
test
test
test
Stop
Start
test
test
test
[Finished in 9.5s]
Comme vous le voyez sur start
, nous commençons simplement la tâche qui appelle certaines fonctions et dort un certain temps en boucle infinie. Sur stop
, nous venons d'annuler cette tâche. Notez que cette tâche doit être arrêtée au moment où le programme est terminé.
Une autre chose importante est que votre rappel ne devrait pas prendre beaucoup de temps à être exécuté (sinon il gèlera votre boucle d’événements). Si vous prévoyez d'appeler des func
de longue durée, vous aurez probablement besoin de pour les exécuter dans l'exécuteur .
Il n'y a pas de support intégré pour les appels périodiques, non.
Créez simplement votre propre boucle de planificateur qui dort et exécute toutes les tâches planifiées:
import math, time
async def scheduler():
while True:
# sleep until the next whole second
now = time.time()
await asyncio.sleep(math.ceil(now) - now)
# execute any scheduled tasks
await for task in scheduled_tasks(time.time()):
await task()
L'itérateur scheduled_tasks()
doit générer des tâches prêtes à être exécutées à l'heure indiquée. Notez que la production de l’horaire et le lancement de toutes les tâches peuvent en principe durer plus d’une seconde; L'idée ici est que le planificateur génère toutes les tâches qui auraient dû être lancées depuis le dernier contrôle.
Version alternative avec décorateur pour python 3.7
import asyncio
import time
def periodic(period):
def scheduler(fcn):
async def wrapper(*args, **kwargs):
while True:
asyncio.create_task(fcn(*args, **kwargs))
await asyncio.sleep(period)
return wrapper
return scheduler
@periodic(2)
async def do_something(*args, **kwargs):
await asyncio.sleep(5) # Do some heavy calculation
print(time.time())
if __== '__main__':
asyncio.run(do_something('Maluzinha do papai!', secret=42))
Une variante qui peut être utile: si vous souhaitez que votre appel périodique se produise toutes les n secondes au lieu de n secondes entre la fin de la dernière exécution et le début de la suivante et que vous ne voulez pas que les appels se chevauchent, procédez comme suit: est plus simple:
async def repeat(interval, func, *args, **kwargs):
"""Run func every interval seconds.
If func has not finished before *interval*, will run again
immediately when the previous iteration finished.
*args and **kwargs are passed as the arguments to func.
"""
while True:
await asyncio.gather(
func(*args, **kwargs),
asyncio.sleep(interval),
)
Et un exemple d'utilisation de ce logiciel pour exécuter quelques tâches en arrière-plan:
async def f():
await asyncio.sleep(1)
print('Hello')
async def g():
await asyncio.sleep(0.5)
print('Goodbye')
async def main():
t1 = asyncio.ensure_future(repeat(3, f))
t2 = asyncio.ensure_future(repeat(2, g))
await t1
await t2
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Basé sur @A. Réponse de Jesse Jiryu Davis (avec les commentaires de @Torkel Bjørnson-Langen et @ReWrite), il s'agit d'une amélioration qui évite la dérive.
import time
import asyncio
@asyncio.coroutine
def periodic(period):
def g_tick():
t = time.time()
count = 0
while True:
count += 1
yield max(t + count * period - time.time(), 0)
g = g_tick()
while True:
print('periodic', time.time())
yield from asyncio.sleep(next(g))
loop = asyncio.get_event_loop()
task = loop.create_task(periodic(1))
loop.call_later(5, task.cancel)
try:
loop.run_until_complete(task)
except asyncio.CancelledError:
pass