web-dev-qa-db-ja.com

asyncioを使用する場合、イベントループをシャットダウンする前に、実行中のすべてのタスクを終了するにはどうすればよいですか

私は次のコードを持っています:

@asyncio.coroutine
def do_something_periodically():
    while True:
        asyncio.async(my_expensive_operation())
        yield from asyncio.sleep(my_interval)
        if shutdown_flag_is_set:
            print("Shutting down")
            break

完了するまでこの関数を実行します。この問題は、シャットダウンが設定されているときに発生します。機能が完了し、保留中のタスクが実行されることはありません。 (これはエラーとして表示されます

task: <Task pending coro=<report() running at script.py:33> wait_for=<Future pending cb=[Task._wakeup()]>>

)。シャットダウンを正しくスケジュールするにはどうすればよいですか?

コンテキストを説明するために、5秒ごとに/ proc/statから読み取り、その期間のCPU使用量を計算し、結果をサーバーに送信するシステムモニターを作成しています。 sigtermを受信するまでこれらの監視ジョブのスケジューリングを続け、スケジューリングを停止し、現在のすべてのジョブが終了するのを待って、正常に終了します。

43
derekdreery

未完了のタスクを取得し、完了するまでループを再度実行してから、ループを閉じるか、プログラムを終了できます。

pending = asyncio.Task.all_tasks()
loop.run_until_complete(asyncio.gather(*pending))
  • pendingは、保留中のタスクのリストです。
  • asyncio.gather()を使用すると、複数のタスクを一度に待機できます。

コルーチン内ですべてのタスクを完了したい場合(「メイン」コルーチンがある場合)、たとえば次のようにできます。

@asyncio.coroutine
def do_something_periodically():
    while True:
        asyncio.async(my_expensive_operation())
        yield from asyncio.sleep(my_interval)
        if shutdown_flag_is_set:
            print("Shutting down")
            break

    yield from asyncio.gather(*asyncio.Task.all_tasks())

また、この場合、すべてのタスクが同じコルーチンで作成されているため、タスクに既にアクセスできます。

@asyncio.coroutine
def do_something_periodically():
    tasks = []
    while True:
        tasks.append(asyncio.async(my_expensive_operation()))
        yield from asyncio.sleep(my_interval)
        if shutdown_flag_is_set:
            print("Shutting down")
            break

    yield from asyncio.gather(*tasks)
42
Martin Richard

Python 3.7の時点で、上記の回答では複数の非推奨API(asyncio.asyncとTask.all_tasks、@ asyncio.coroutine、yield fromなど)を使用しているため、むしろこれを使用してください:

import asyncio


async def my_expensive_operation(expense):
    print(await asyncio.sleep(expense, result="Expensive operation finished."))


async def do_something_periodically(expense, interval):
    while True:
        asyncio.create_task(my_expensive_operation(expense))
        await asyncio.sleep(interval)


loop = asyncio.get_event_loop()
coro = do_something_periodically(1, 1)

try:
    loop.run_until_complete(coro)
except KeyboardInterrupt:
    coro.close()
    tasks = asyncio.all_tasks(loop)
    expensive_tasks = {task for task in tasks if task._coro.__!= coro.__name__}
    loop.run_until_complete(asyncio.gather(*expensive_tasks))

asyncio.shield の使用を検討することもできますが、この方法では[〜#〜] all [〜#〜]を取得できません実行中のタスクは終了しましたが、shieldedのみです。ただし、一部のシナリオでは依然として有用です。

それに加えて、Python 3.7では、高レベルAPIメソッド asynio.run を使用することもできます。AsPython core開発者、Yury Selivanovは次のように提案しています: https://youtu.be/ReXxO_azV-w?t=636
注:asyncio.run関数がPython 3.7暫定ベースでasyncioに追加されました。

お役に立てば幸いです!

import asyncio


async def my_expensive_operation(expense):
    print(await asyncio.sleep(expense, result="Expensive operation finished."))


async def do_something_periodically(expense, interval):
    while True:
        asyncio.create_task(my_expensive_operation(expense))
        # using asyncio.shield
        await asyncio.shield(asyncio.sleep(interval))


coro = do_something_periodically(1, 1)

if __== "__main__":
    try:
        # using asyncio.run
        asyncio.run(coro)
    except KeyboardInterrupt:
        print('Cancelled!')
1

戻る前に保留中のタスクカウントが1になるまで待機するラッパーコルーチンを使用します。

async def loop_job():
    asyncio.create_task(do_something_periodically())
    while len(asyncio.Task.all_tasks()) > 1:  # Any task besides loop_job() itself?
        await asyncio.sleep(0.2)

asyncio.run(loop_job())
0
gilch

これがあなたが求めているものかどうかはわかりませんが、私は同様の問題を抱えていました。ここに私が思いついた究極の解決策があります。

コードはpython 3と互換性があり、パブリックasyncio APIのみを使用します(つまり、ハッキー_coroおよび廃止されたAPIはありません)。

import asyncio

async def fn():
  await asyncio.sleep(1.5)
  print('fn')

async def main():
    print('main start')
    asyncio.create_task(fn()) # run in parallel
    await asyncio.sleep(0.2)
    print('main end')


def async_run_and_await_all_tasks(main):
  def get_pending_tasks():
      tasks = asyncio.Task.all_tasks()
      pending = [task for task in tasks if task != run_main_task and not task.done()]
      return pending

  async def run_main():
      await main()

      while True:
          pending_tasks = get_pending_tasks()
          if len(pending_tasks) == 0: return
          await asyncio.gather(*pending_tasks)

  loop = asyncio.new_event_loop()
  run_main_coro = run_main()
  run_main_task = loop.create_task(run_main_coro)
  loop.run_until_complete(run_main_task)

# asyncio.run(main()) # doesn't print from fn task, because main finishes earlier
async_run_and_await_all_tasks(main)

出力(予想どおり):

main start
main end
fn

このasync_run_and_await_all_tasks関数は、nodejsのように動作するためにpythonを作成します。未完了のタスクがない場合にのみ終了します。

0
grabantot