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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 08:19:33  来源:igfitidea点击:

Asynchronous exception handling in Python

pythonpython-3.xpython-asyncio

提问by Yury Bayda

I've the following code using asyncioand aiohttpto make asynchronous HTTP requests.

我有以下代码使用asyncioaiohttp发出异步 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 getfunction 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.waitdoesn't actually consume the Futurespassed to it, it just waits for them to complete, and then returns the Futureobjects:

asyncio.wait实际上并不消耗Futures传递给它的,它只是等待它们完成,然后返回Future对象:

coroutineasyncio.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 fromthe items in the donelist, 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 Futurethat aggregates all their results (or raises the first Exceptionthrown 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.gatheractually 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_exceptionsis True, 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_exceptionsTrue,则任务中的异常与成功结果相同,并收集到结果列表中;否则,第一个引发的异常将立即传播到返回的未来。

回答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()
    )