Python Asyncio-RuntimeError:无法关闭正在运行的事件循环

时间:2018-08-15 16:05:08

标签: python python-3.6 python-asyncio aiohttp

我正在尝试解决此错误: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()

更新:

我摆脱了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()

1 个答案:

答案 0 :(得分:1)

要回答最初提出的问题,不需要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的{​​{3}}不同,特别是因为它是由asyncio本身引发的。 KeyboardInterrupt可以通过run_until_complete传播,而实际上还没有完成。在asyncio中正确处理 Ctrl-C 是非常困难的,甚至是不可能的(有关详细信息,请参见referenced answer),但是幸运的是,问题根本不是关于 Ctrl-C ,这是关于协程引发的异常。 (请注意,KeyboardInterrupt并非继承自Exception,因此,在 Ctrl-C 的情况下,例外正文甚至不会执行。)

  

我之所以取消未来,是因为在这种情况下,还有其他待处理的任务,我想从本质上删除那些任务并开始新的事件循环。

这是一件正确的事情,但是(更新的)问题中的代码仅取消了一个已传递给run_until_complete的未来。回想一下,future是将在以后提供的结果值的占位符。提供该值后,可以通过调用future.result()来检索它。如果将来的“值”是一个例外,则future.result()将引发该例外。 run_until_complete的约定是,它将在给定的未来产生值之前一直运行事件循环,然后返回该值。如果“值”实际上是要提出的例外情况,则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)

当所讨论的将来实际上是here(将协程包装到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的{​​{1}}表达式。除非任务专门捕获并抑制了此异常(行为良好的异步代码不应该这样做),否则该任务将停止执行,并且CancelledError将成为其结果。但是,如果在调用CancelledError时协程已经完成,则cancel()无法执行任何操作,因为没有待处理的cancel()注入await