J'ai le code suivant en utilisant asyncio
et aiohttp
pour effectuer des requêtes HTTP asynchrones.
import sys
import asyncio
import aiohttp
@asyncio.coroutine
def get(url):
try:
print('GET %s' % url)
resp = yield from aiohttp.request('GET', url)
except Exception as e:
raise Exception("%s has error '%s'" % (url, e))
else:
if resp.status >= 400:
raise Exception("%s has error '%s: %s'" % (url, resp.status, resp.reason))
return (yield from resp.text())
@asyncio.coroutine
def fill_data(run):
url = 'http://www.google.com/%s' % run['name']
run['data'] = yield from get(url)
def get_runs():
runs = [ {'name': 'one'}, {'name': 'two'} ]
loop = asyncio.get_event_loop()
task = asyncio.wait([fill_data(r) for r in runs])
loop.run_until_complete(task)
return runs
try:
get_runs()
except Exception as e:
print(repr(e))
sys.exit(1)
Pour une raison quelconque, les exceptions déclenchées à l'intérieur de la fonction get
ne sont pas interceptées:
Future/Task exception was never retrieved
Traceback (most recent call last):
File "site-packages/asyncio/tasks.py", line 236, in _step
result = coro.send(value)
File "mwe.py", line 25, in fill_data
run['data'] = yield from get(url)
File "mwe.py", line 17, in get
raise Exception("%s has error '%s: %s'" % (url, resp.status, resp.reason))
Exception: http://www.google.com/two has error '404: Not Found'
Alors, quelle est la bonne façon de gérer les exceptions soulevées par les couroutines?
asyncio.wait
Ne consomme pas réellement le Futures
qui lui est passé, il attend juste qu'ils se terminent, puis retourne les objets Future
:
coroutine
asyncio.wait(futures, *, loop=None, timeout=None, return_when=ALL_COMPLETED)
Attendez que les objets Futures et coroutine donnés par les futurs séquences se terminent. Les coroutines seront enveloppées dans des tâches. Renvoie deux ensembles de
Future
: (terminé, en attente).
Jusqu'à ce que vous ayez réellement yield from
Les éléments de la liste done
, ils ne seront pas consommés. Étant donné que votre programme se ferme sans consommer les futurs, vous voyez les messages "exception n'a jamais été récupérée".
Pour votre cas d'utilisation, il est probablement plus logique d'utiliser asyncio.gather
, qui consommera réellement chaque Future
, puis renverra un seul Future
qui agrège tous leurs résultats (ou lève le premier Exception
lancé par un futur dans la liste d'entrée).
def get_runs():
runs = [ {'name': 'one'}, {'name': 'two'} ]
loop = asyncio.get_event_loop()
tasks = asyncio.gather(*[fill_data(r) for r in runs])
loop.run_until_complete(tasks)
return runs
Sortie:
GET http://www.google.com/two
GET http://www.google.com/one
Exception("http://www.google.com/one has error '404: Not Found'",)
Notez que asyncio.gather
Vous permet en fait de personnaliser son comportement lorsque l'un des futurs lève une exception; le comportement par défaut consiste à déclencher la première exception qu'il frappe, mais il peut également simplement renvoyer chaque objet d'exception dans la liste de sortie:
asyncio.gather(*coros_or_futures, loop=None, return_exceptions=False)
Retourne un futur regroupant les résultats des objets ou futurs coroutine donnés.
Tous les futures doivent partager la même boucle d'événements. Si toutes les tâches sont effectuées avec succès, le résultat futur renvoyé est la liste des résultats (dans l’ordre de la séquence d’origine, pas nécessairement dans l’ordre d’arrivée des résultats). Si
return_exceptions
EstTrue
, les exceptions dans les tâches sont traitées de la même manière que les résultats réussis et rassemblées dans la liste des résultats; sinon, la première exception levée sera immédiatement propagée dans le futur retourné.
Pour déboguer ou "gérer" les exceptions dans rappel :
Coroutine qui retourne un résultat ou déclenche des exceptions:
@asyncio.coroutine
def async_something_entry_point(self):
try:
return self.real_stuff_which_throw_exceptions()
except:
raise Exception(some_identifier_here + ' ' + traceback.format_exc())
Et rappel:
def callback(self, future: asyncio.Future):
exc = future.exception()
if exc:
# Handle wonderful empty TimeoutError exception
if type(exc) == TimeoutError:
self.logger('<Some id here> callback exception TimeoutError')
else:
self.logger("<Some id here> callback exception " + str(exc))
# store your result where you want
self.result.append(
future.result()
)