web-dev-qa-db-ja.com

Python-非同期/コルーチンを使用したタイマー

実行中のプロセスを中断し、起動時にコルーチンを呼び出すタイマーを設定しようとしています。しかし、これを達成するための正しい方法が何であるかはわかりません。私はthreading.Timerと一緒にAbstractEventLoop.call_laterを見つけましたが、どちらも機能していないようです(または私はそれらを間違って使用しています)。コードはかなり基本的で、次のようになります。

def set_timer( time ):
    self.timer = Timer( 10.0, timeout )
    self.timer.start()
    #v2
    #self.timer = get_event_loop()
    #self.timer.call_later( 10.0, timeout )
    return

async def timeout():
    await some_func()
    return

数秒後にコールバック関数を呼び出す非ブロッキングタイマーを設定する正しい方法は何ですか?タイマーをキャンセルできることはボーナスですが、必須ではありません。私が必要とする主なものは、ブロッキングを行わず、コルーチンを正常に呼び出すことです。現在、オブジェクトを待機できない(待機を投げた場合)、またはsome_funcが待機されなかった、期待される出力が発生しないというエラーが返されます。

5
Greg Miller

Task を使用して ensure_future を作成することは、実行フローをブロックせずにジョブの実行を開始する一般的な方法です。 キャンセル タスクも実行できます。

私はあなたが何かを始めるための実装例を書きました:

import asyncio


class Timer:
    def __init__(self, timeout, callback):
        self._timeout = timeout
        self._callback = callback
        self._task = asyncio.ensure_future(self._job())

    async def _job(self):
        await asyncio.sleep(self._timeout)
        await self._callback()

    def cancel(self):
        self._task.cancel()


async def timeout_callback():
    await asyncio.sleep(0.1)
    print('echo!')


async def main():
    print('\nfirst example:')
    timer = Timer(2, timeout_callback)  # set timer for two seconds
    await asyncio.sleep(2.5)  # wait to see timer works

    print('\nsecond example:')
    timer = Timer(2, timeout_callback)  # set timer for two seconds
    await asyncio.sleep(1)
    timer.cancel()  # cancel it
    await asyncio.sleep(1.5)  # and wait to see it won't call callback


loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
    loop.run_until_complete(main())
finally:
    loop.run_until_complete(loop.shutdown_asyncgens())
    loop.close()

出力:

first example:
echo!

second example:
12

ミハイル・ゲラシモフの回答に感謝します。とても役に立ちました。これがミハイルの答えの拡張です。これは、いくつかのひねりを加えたインターバルタイマーです。おそらくそれは一部のユーザーにとっては便利です。

import asyncio


class Timer:
    def __init__(self, interval, first_immediately, timer_name, context, callback):
        self._interval = interval
        self._first_immediately = first_immediately
        self._name = timer_name
        self._context = context
        self._callback = callback
        self._is_first_call = True
        self._ok = True
        self._task = asyncio.ensure_future(self._job())
        print(timer_name + " init done")

    async def _job(self):
        try:
            while self._ok:
                if not self._is_first_call or not self._first_immediately:
                    await asyncio.sleep(self._interval)
                await self._callback(self._name, self._context, self)
                self._is_first_call = False
        except Exception as ex:
            print(ex)

    def cancel(self):
        self._ok = False
        self._task.cancel()


async def some_callback(timer_name, context, timer):
    context['count'] += 1
    print('callback: ' + timer_name + ", count: " + str(context['count']))

    if timer_name == 'Timer 2' and context['count'] == 3:
        timer.cancel()
        print(timer_name + ": goodbye and thanks for all the fish")


timer1 = Timer(interval=1, first_immediately=True, timer_name="Timer 1", context={'count': 0}, callback=some_callback)
timer2 = Timer(interval=5, first_immediately=False, timer_name="Timer 2", context={'count': 0}, callback=some_callback)

try:
    loop = asyncio.get_event_loop()
    loop.run_forever()
except KeyboardInterrupt:
    timer1.cancel()
    timer2.cancel()
    print("clean up done")
2
Helgi Borg