tornado
からasyncio
に移行していますが、asyncio
のtornado
に相当するPeriodicCallback
が見つかりません。 (PeriodicCallback
は、実行する関数と呼び出し間のミリ秒数の2つの引数を取ります。)
asyncio
にそのような同等物はありますか?RecursionError
を取得するリスクを冒さずにこれを実装する最もクリーンな方法は何ですか?3.5未満のPythonバージョンの場合:
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
Python 3.5以降の場合:
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
Asyncioプログラムの「バックグラウンド」で何かが発生すると思われる場合は、asyncio.Task
を使用するのが適切な方法です。 この投稿 を読んで、タスクの操作方法を確認してください。
定期的にいくつかの機能を実行するクラスの可能な実装は次のとおりです。
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()
テストしてみましょう:
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())
出力:
Start
test
test
test
Stop
Start
test
test
test
[Finished in 9.5s]
start
を見るとわかるように、いくつかの関数を呼び出し、無限ループでしばらくスリープするタスクを開始するだけです。 stop
で、そのタスクをキャンセルします。プログラムが終了した時点でタスクを停止する必要があることに注意してください。
もう1つ重要なことは、コールバックの実行にそれほど時間がかからないことです(または、イベントループがフリーズします)。長時間実行されるfunc
を呼び出す予定がある場合は、おそらく executorで実行するために が必要になります。
定期的な呼び出しに対する組み込みのサポートはありません。
スケジュールされたタスクをスリープして実行する独自のスケジューラループを作成するだけです。
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()
scheduled_tasks()
イテレータは、指定された時間に実行する準備ができているタスクを生成する必要があります。スケジュールの作成とすべてのタスクの開始には、理論的には1秒以上かかる場合があります。ここでのアイデアは、スケジューラが最後のチェック以降に開始する必要があるすべてのタスクを生成することです。
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))
役立つ可能性のあるバリアント:最後の実行の終了から次の実行の開始までの間にn秒ではなくn秒ごとに繰り返し呼び出しが発生するようにし、呼び出しが時間的に重複しないようにするには、次のようにします。より簡単です:
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),
)
そして、それを使用してバックグラウンドでいくつかのタスクを実行する例:
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())
@Aに基づきます。 Jesse Jiryu Davisの応答(@TorkelBjørnson-Langenおよび@ReWriteのコメント付き)これはドリフトを回避する改善です。
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