2015-05-20 174 views
25

我使用asyncioaiohttp来制作异步HTTP请求。该get函数内部提出Python中的异步异常处理

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) 

出于某种原因,异常没有抓到:

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引发的异常正确的方式?

回答

28

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

协程asyncio.wait(futures, *, loop=None, timeout=None, return_when=ALL_COMPLETED)

等待序列期货给出的期货和协同对象 完成。协程将在任务中包装 。返回两组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)

回到未来从给定的协同程序汇总结果对象 或期货。

所有期货必须共享相同的事件循环。如果所有任务都成功完成 ,返回的未来结果是结果列表( 原始序列的顺序,不一定是 结果到达的顺序)。如果return_exceptionsTrue,则 任务中的例外与成功的结果相同,并收集在 结果列表中;否则,第一个异常将立即传播到返回的将来 。

+0

谢谢你的解释,该文档并不完全清楚异常处理 – megabyde

+0

那么将如何用'wait'做呢?是否像'asyncio.wait(...)'的产量?应该“等待asyncio.wait(...)'工作吗? – z0r

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