web-dev-qa-db-ja.com

Python Asyncio-RuntimeError:実行中のイベントループを閉じることができません

このエラーを解決しようとしています:asyncioプロセスの_RuntimeError: Cannot close a running event loop_。タスクがまだ保留されているときに障害が発生したために、それが起こっていると思います。その後、イベントループを閉じようとします。イベントループを閉じる前に残りの応答を待つ必要があると思いますが、特定の状況でそれを正しく実行する方法がわかりません。

_ def start_job(self):

        if self.auth_expire_timestamp < get_timestamp():
            api_obj = api_handler.Api('Api Name', self.dbObj)
            self.api_auth_resp = api_obj.get_auth_response()
            self.api_attr = api_obj.get_attributes()


        try:
            self.queue_manager(self.do_stuff(json_data))
        except aiohttp.ServerDisconnectedError as e:
            logging.info("Reconnecting...")
            api_obj = api_handler.Api('API Name', self.dbObj)
            self.api_auth_resp = api_obj.get_auth_response()
            self.api_attr = api_obj.get_attributes()
            self.run_eligibility()

async def do_stuff(self, data):

    tasks = []

    async with aiohttp.ClientSession() as session:
        for row in data:
            task = asyncio.ensure_future(self.async_post('url', session, row))
            tasks.append(task)
        result = await asyncio.gather(*tasks)
    self.load_results(result)


def queue_manager(self, method):
    self.loop = asyncio.get_event_loop()
    future = asyncio.ensure_future(method)
    self.loop.run_until_complete(future)


async def async_post(self, resource, session, data):
        async with session.post(self.api_attr.api_endpoint + resource, headers=self.headers, data=data) as response:
            resp = []
            try:
                headers = response.headers['foo']
                content = await response.read()
                resp.append(headers)
                resp.append(content)
            except KeyError as e:
                logging.error('KeyError at async_post response')
                logging.error(e)
        return resp


def shutdown(self):
    //need to do something here to await the remaining tasks and then I need to re-start a new event loop, which i think i can do, just don't know how to appropriately stop the current one.
    self.loop.close() 
    return True
_

エラーを処理し、イベントループを適切に閉じて、新しいループを開始し、基本的にプログラム全体を再起動して続行できるようにする方法を教えてください。

編集:

これは this SO answer に基づいて、私が今試していることです。残念ながら、このエラーはまれにしか発生しないため、強制できない限り、 _queue_manager_メソッドで、これを次のように変更しました。

_try:
   self.loop.run_until_complete(future)
except Exception as e:
   future.cancel()
   self.loop.run_until_complete(future)
   future.exception()
_

UPDATE:

私はshutdown()メソッドを削除して、これをqueue_manager()メソッドに追加しましたが、問題なく動作しているようです:

_  try:
        self.loop.run_until_complete(future)
    except Exception as e:
        future.cancel()
        self.check_in_records()
        self.reconnect()
        self.start_job()
        future.exception()
_
6
hyphen

最初に述べた質問に答えるには、実行中のループをclose()する必要はありません。プログラム全体で同じループを再利用できます。

更新のコードを考えると、_queue_manager_は次のようになります。

_try:
    self.loop.run_until_complete(future)
except Exception as e:
    self.check_in_records()
    self.reconnect()
    self.start_job()
_

futureをキャンセルする必要はなく、私が知る限り効果はありません。これは、特別にKeyboardInterruptに反応する 参照される回答 とは異なります。これは、asyncio自体によって発生するため、特別です。 KeyboardInterruptは、将来が実際に完了することなく_run_until_complete_によって伝達できます。取り扱い Ctrl-C asyncioで正しく実行することは非常に困難または不可能ですらあります(詳細は here を参照してください)。 Ctrl-C まったく、それはコルーチンによって発生した例外についてです。 (KeyboardInterruptExceptionを継承しないため、 Ctrl-C except本体は実行されません。)

このインスタンスには保留中のタスクが残っており、基本的にそれらのタスクを削除して新しいイベントループを開始したいので、私は未来をキャンセルしていました。

これは正しいことですが、(更新された)質問のコードは単一のフューチャー、つまり_run_until_complete_に既に渡されたフューチャーをキャンセルするだけです。 futureは後で提供される結果値のプレースホルダーであることを思い出してください。値が提供されると、future.result()を呼び出して取得できます。将来の「値」が例外である場合、future.result()はその例外を発生させます。 _run_until_complete_は、指定されたfutureが値を生成するのにかかる限り、イベントループを実行し、その値を返すという規約を持っています。 「値」が実際に発生する例外である場合、_run_until_complete_はそれを再発生させます。例えば:

_loop = asyncio.get_event_loop()
fut = loop.create_future()
loop.call_soon(fut.set_exception, ZeroDivisionError)
# raises ZeroDivisionError, as that is the future's result,
# manually set
loop.run_until_complete(fut)
_

問題のフューチャーが実際にコルーチンをTaskにラップする非同期固有のオブジェクトである Future である場合、そのようなフューチャーの結果はコルーチンによって返されるオブジェクトです。コルーチンが例外を発生させた場合、結果を取得するとそれが再発生し、そのため_run_until_complete_も発生します。

_async def fail():
    1/0

loop = asyncio.get_event_loop()
fut = loop.create_task(fail())
# raises ZeroDivisionError, as that is the future's result,
# because the coroutine raises it
loop.run_until_complete(fut)
_

タスクを処理する場合、_run_until_complete_終了は、コルーチンも終了したことを意味します。これは、_run_until_complete_の復帰または発生により、値を返すか、例外を発生させます。

一方、タスクのキャンセルは、再開するタスクと、タスクを中断してawaitを発生させるCancelledError式を準備することで機能します。タスクがこの例外を明確にキャッチして抑制しない限り(正常に動作するasyncioコードでは実行されないはずです)、タスクは実行を停止し、CancelledErrorがその結果になります。ただし、cancel()が呼び出されたときにコルーチンがすでに終了している場合、awaitを挿入する保留中のCancelledErrorがないため、cancel()は何もできません。

2
user4815162342