我使用asyncio
和aiohttp
创建了异步HTTP请求的以下代码。
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)
出于某种原因,get
函数内引发的异常未被捕获:
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'
那么,处理由couroutines引发的异常的正确方法是什么?
答案 0 :(得分:42)
asyncio.wait
实际上并没有使用传递给它的Futures
,只是等待它们完成,然后返回Future
个对象:
coroutine
asyncio.wait(futures, *, loop=None, timeout=None, return_when=ALL_COMPLETED)
等待Futures和coroutine对象 由序列期货给出来完成。协同程序将被包裹 在任务中。返回两组
Future
:(已完成,待定)。
在您实际yield from
done
列表中的项目之前,它们仍将保持未使用状态。由于你的程序退出而没有消费未来,你会看到"异常从未被检索过"消息。
对于您的用例,使用asyncio.gather
可能更有意义,它实际上会消耗每个Future
,然后返回一个聚合其所有结果的Future
(或引发输入列表中未来抛出的第一个Exception
。
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
输出:
GET http://www.google.com/two
GET http://www.google.com/one
Exception("http://www.google.com/one has error '404: Not Found'",)
请注意,asyncio.gather
实际上允许您在其中一个期货引发异常时自定义其行为;默认行为是引发它命中的第一个异常,但它也可以只返回输出列表中的每个异常对象:
<强>
asyncio.gather(*coros_or_futures, loop=None, return_exceptions=False)
强>返回给定协同程序对象的未来聚合结果 或期货。
所有期货必须共享相同的事件循环。如果所有任务都完成了 成功,返回的未来结果是结果列表(in 原始序列的顺序,不一定是顺序 结果到达)。 如果
return_exceptions
为True
,则表示例外情况 任务被视为成功的结果,并收集在 结果清单;否则,第一个提出的例外将立即生效 传播到返回的未来。
答案 1 :(得分:2)
调试或“处理”callback中的例外:
返回某些结果或提出异常的协程:
@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())
回调:
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()
)
答案 2 :(得分:-2)
asyncio提供用于自定义错误处理的API,请参阅文档https://docs.python.org/3/library/asyncio-eventloop.html#error-handling-api