web-dev-qa-db-fra.com

Python Asyncio - RuntimeError: impossible de fermer une boucle d'événement en cours d'exécution

J'essaie de résoudre cette erreur: RuntimeError: Cannot close a running event loop dans mon processus asyncologique. Je crois que cela se produit car il y a un échec alors que des tâches sont toujours en attente, puis j'essaie de fermer la boucle d'événements. Je pense que je dois attendre les réponses restantes avant de fermer la boucle d'événements, mais je ne sais pas comment y parvenir correctement dans ma situation spécifique.

 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

Comment puis-je gérer l'erreur et fermer correctement la boucle d'événement afin de pouvoir en démarrer une nouvelle et essentiellement redémarrer le programme entier et continuer.

EDIT:

C’est ce que j’essaie maintenant, à partir de this SO answer . Malheureusement, cette erreur ne se produit que rarement, donc à moins que je ne puisse la forcer, je devrai attendre et voir si cela fonctionne. Dans ma méthode queue_manager je l'ai changé en ceci:

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

UPDATE:

Je me suis débarrassé de la méthode shutdown() et l'ai ajouté à ma méthode queue_manager() et cela semble fonctionner sans problème: 

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

Pour répondre à la question telle qu'indiquée à l'origine, il n'est pas nécessaire de close() une boucle en cours d'exécution, vous pouvez réutiliser la même boucle pour l'ensemble du programme.

Étant donné le code de la mise à jour, votre queue_manager pourrait ressembler à ceci:

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

Annuler future n'est pas nécessaire et, autant que je sache, n'a aucun effet. Ceci est différent de réponse référencée qui réagit spécifiquement à KeyboardInterrupt, spécial parce qu’il est soulevé par asyncio lui-même. KeyboardInterrupt peut être propagé par run_until_complete sans que le futur soit réellement terminé. Manipulation Ctrl-C correctement en asyncio est très difficile, voire impossible (voir ici pour plus de détails), mais heureusement, la question ne concerne pas Ctrl-C du tout, il s'agit d'exceptions soulevées par la coroutine. (Notez que KeyboardInterrupt n’hérite pas de Exception, donc en cas de Ctrl-C le corps d'exception ne sera même pas exécuté.)

J'annulais l'avenir car, dans ce cas, il reste des tâches en attente et je souhaite essentiellement les supprimer et commencer une nouvelle boucle d'événements.

C'est une bonne chose à vouloir faire, mais le code dans la question (mise à jour) n'annule qu'un seul futur, celui déjà passé à run_until_complete. Rappelez-vous qu'un avenir est un espace réservé pour une valeur de résultat qui sera fournie ultérieurement. Une fois que la valeur est fournie, elle peut être récupérée en appelant future.result(). Si la "valeur" du futur est une exception, future.result() lèvera cette exception. run_until_complete a le contrat selon lequel elle exécutera la boucle d'événements aussi longtemps qu'il faudra au futur donné pour générer une valeur, puis elle retournera cette valeur. Si la "valeur" est en fait une exception à lever, alors run_until_complete la relancera. Par exemple:

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)

Lorsque le futur en question est en fait un Task , un objet asyncio-spécifique qui englobe une coroutine dans une Future, le résultat de ce futur est l'objet renvoyé par la coroutine. Si la coroutine déclenche une exception, la récupération du résultat le relancera, de même que 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)

Lorsque vous traitez une tâche, run_until_complete finish signifie que la coroutine est également terminée, qu'elle ait renvoyé une valeur ou levé une exception, comme déterminé par run_until_complete retournant ou levant.

D'autre part, l'annulation d'une tâche fonctionne en organisant la reprise de la tâche et l'expression await qui l'a suspendue pour générer CancelledError. À moins que la tâche ne capture et ne supprime spécifiquement cette exception (ce qu'un code asyncio bien comporté n'est pas censé faire), la tâche cesse de s'exécuter et le résultat est CancelledError. Cependant, si la coroutine est déjà terminée lorsque cancel() est appelé, alors cancel() ne peut rien faire car il n'y a pas de await en attente dans lequel injecter CancelledError.

1
user4815162342