Python 中的异步异常处理
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30361824/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Asynchronous exception handling in Python
提问by Yury Bayda
I've the following code using asyncio
and aiohttp
to make asynchronous HTTP requests.
我有以下代码使用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)
For some reason, exceptions raised inside the get
function are not caught:
出于某种原因,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'
So, what is correct way to handle exceptions raised by couroutines?
那么,处理协程引发的异常的正确方法是什么?
采纳答案by dano
asyncio.wait
doesn't actually consume the Futures
passed to it, it just waits for them to complete, and then returns the Future
objects:
asyncio.wait
实际上并不消耗Futures
传递给它的,它只是等待它们完成,然后返回Future
对象:
coroutine
asyncio.wait(futures, *, loop=None, timeout=None, return_when=ALL_COMPLETED)
Wait for the Futures and coroutine objects given by the sequence futures to complete. Coroutines will be wrapped in Tasks. Returns two sets of
Future
: (done, pending).
协程
asyncio.wait(futures, *, loop=None, timeout=None, return_when=ALL_COMPLETED)
等待序列 future 给出的 Futures 和协程对象完成。协程将被包装在任务中。返回两组
Future
:(完成,待定)。
Until you actually yield from
the items in the done
list, they'll remain unconsumed. Since your program exits without consuming the futures, you see the "exception was never retrieved" messages.
直到您真正找到列表中yield from
的项目done
,它们才会被消耗掉。由于您的程序在不消耗期货的情况下退出,您会看到“从未检索到异常”消息。
For your use-case, it probably makes more sense to use asyncio.gather
, which will actually consume each Future
, and then return a single Future
that aggregates all their results (or raises the first Exception
thrown by a future in the input list).
对于您的用例,使用 可能更有意义asyncio.gather
,它实际上会消耗 each 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
Output:
输出:
GET http://www.google.com/two
GET http://www.google.com/one
Exception("http://www.google.com/one has error '404: Not Found'",)
Note that asyncio.gather
actually lets you customize its behavior when one of the futures raises an exception; the default behavior is to raise the first exception it hits, but it can also just return each exception object in the output list:
请注意,asyncio.gather
当其中一个期货引发异常时,实际上可以让您自定义其行为;默认行为是引发它遇到的第一个异常,但它也可以只返回输出列表中的每个异常对象:
asyncio.gather(*coros_or_futures, loop=None, return_exceptions=False)
Return a future aggregating results from the given coroutine objects or futures.
All futures must share the same event loop. If all the tasks are done successfully, the returned future's result is the list of results (in the order of the original sequence, not necessarily the order of results arrival). If
return_exceptions
isTrue
, exceptions in the tasks are treated the same as successful results, and gathered in the result list; otherwise, the first raised exception will be immediately propagated to the returned future.
asyncio.gather(*coros_or_futures, loop=None, return_exceptions=False)
从给定的协程对象或期货返回未来聚合结果。
所有期货必须共享相同的事件循环。如果所有任务都成功完成,返回的future的结果就是结果列表(按照原来的顺序,不一定是结果到达的先后顺序)。如果
return_exceptions
是True
,则任务中的异常与成功结果相同,并收集到结果列表中;否则,第一个引发的异常将立即传播到返回的未来。
回答by Oleg Neumyvakin
To debug or "handle" exceptions in callback:
要在回调中调试或“处理”异常:
Coroutine which return some result or raise 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())
And callback:
和回调:
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()
)