1. ホーム
  2. python

[解決済み] "Fire and forget" python async/await

2022-04-24 23:56:18

質問

時々、クリティカルではない非同期処理が必要なことがありますが、それが完了するのを待ちたくはないです。 Tornado のコルーチン実装では、単に yield というキーワードがあります。

私は、この新しい「quot;fire & forget"」をどのように使うか考えてみました。 async / await の構文がPython 3.5でリリースされました。 例:簡略化されたコードスニペット。

async def async_foo():
    print("Do some stuff asynchronously here...")

def bar():
    async_foo()  # fire and forget "async_foo()"

bar()

しかし、何が起こるかというと bar() は実行されず、代わりに実行時警告が表示されます。

RuntimeWarning: coroutine 'async_foo' was never awaited
  async_foo()  # fire and forget "async_foo()"

解決方法は?

更新しました。

交換 asyncio.ensure_futureasyncio.create_task Python >= 3.7を使用している場合は、すべての場所で、より新しい、より良い方法です。 タスクを生成する .


asyncio.Taskでquot;fire and forget"。

のpythonドキュメントによると asyncio.Task に対して何らかのコルーチンを起動することが可能です。 をバックグラウンドで実行します。 . で作成したタスクは asyncio.ensure_future は実行をブロックしません (したがって、この関数はすぐに返されます!)。これは、あなたが要求したように、quot;fire and forget"を行う方法のように見えます。

import asyncio


async def async_foo():
    print("async_foo started")
    await asyncio.sleep(1)
    print("async_foo done")


async def main():
    asyncio.ensure_future(async_foo())  # fire and forget async_foo()

    # btw, you can also create tasks inside non-async funcs

    print('Do some actions 1')
    await asyncio.sleep(1)
    print('Do some actions 2')
    await asyncio.sleep(1)
    print('Do some actions 3')


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

出力します。

Do some actions 1
async_foo started
Do some actions 2
async_foo done
Do some actions 3

イベントループが終了した後にタスクが実行された場合はどうなりますか?

asyncioは、イベントループが完了した瞬間にタスクが完了することを期待していることに注意してください。だから、もしあなたが main() に変更します。

async def main():
    asyncio.ensure_future(async_foo())  # fire and forget

    print('Do some actions 1')
    await asyncio.sleep(0.1)
    print('Do some actions 2')

プログラムが終了すると、このような警告が表示されます。

Task was destroyed but it is pending!
task: <Task pending coro=<async_foo() running at [...]

これを防ぐには、単に 保留中のタスクをすべて待機させる イベントループが終了した後に

async def main():
    asyncio.ensure_future(async_foo())  # fire and forget

    print('Do some actions 1')
    await asyncio.sleep(0.1)
    print('Do some actions 2')


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
    
    # Let's also finish all running tasks:
    pending = asyncio.Task.all_tasks()
    loop.run_until_complete(asyncio.gather(*pending))

タスクを待機させるのではなく、終了させる

タスクの実行を待ちたくない場合もあります(例えば、あるタスクは永遠に実行されるように作成されている場合があります)。そのような場合は、単に cancel() を待つ代わりに

import asyncio
from contextlib import suppress


async def echo_forever():
    while True:
        print("echo")
        await asyncio.sleep(1)


async def main():
    asyncio.ensure_future(echo_forever())  # fire and forget

    print('Do some actions 1')
    await asyncio.sleep(1)
    print('Do some actions 2')
    await asyncio.sleep(1)
    print('Do some actions 3')


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

    # Let's also cancel all running tasks:
    pending = asyncio.Task.all_tasks()
    for task in pending:
        task.cancel()
        # Now we should await task to execute it's cancellation.
        # Cancelled task raises asyncio.CancelledError that we can suppress:
        with suppress(asyncio.CancelledError):
            loop.run_until_complete(task)

出力します。

Do some actions 1
echo
Do some actions 2
echo
Do some actions 3
echo